1 /**
  2  * jsBezier
  3  *
  4  * Copyright (c) 2010 - 2017 jsPlumb (hello@jsplumbtoolkit.com)
  5  *
  6  * licensed under the MIT license.
  7  *
  8  * a set of Bezier curve functions that deal with Beziers, used by jsPlumb, and perhaps useful for other people.  These functions work with Bezier
  9  * curves of arbitrary degree.
 10  *
 11  * - functions are all in the 'jsBezier' namespace.
 12  *
 13  * - all input points should be in the format {x:.., y:..}. all output points are in this format too.
 14  *
 15  * - all input curves should be in the format [ {x:.., y:..}, {x:.., y:..}, {x:.., y:..}, {x:.., y:..} ]
 16  *
 17  * - 'location' as used as an input here refers to a decimal in the range 0-1 inclusive, which indicates a point some proportion along the length
 18  * of the curve.  location as output has the same format and meaning.
 19  *
 20  *
 21  * Function List:
 22  * --------------
 23  *
 24  * distanceFromCurve(point, curve)
 25  *
 26  * 	Calculates the distance that the given point lies from the given Bezier.  Note that it is computed relative to the center of the Bezier,
 27  * so if you have stroked the curve with a wide pen you may wish to take that into account!  The distance returned is relative to the values
 28  * of the curve and the point - it will most likely be pixels.
 29  *
 30  * gradientAtPoint(curve, location)
 31  *
 32  * 	Calculates the gradient to the curve at the given location, as a decimal between 0 and 1 inclusive.
 33  *
 34  * gradientAtPointAlongCurveFrom (curve, location)
 35  *
 36  *	Calculates the gradient at the point on the given curve that is 'distance' units from location.
 37  *
 38  * nearestPointOnCurve(point, curve)
 39  *
 40  *	Calculates the nearest point to the given point on the given curve.  The return value of this is a JS object literal, containing both the
 41  *point's coordinates and also the 'location' of the point (see above), for example:  { point:{x:551,y:150}, location:0.263365 }.
 42  *
 43  * pointOnCurve(curve, location)
 44  *
 45  * 	Calculates the coordinates of the point on the given Bezier curve at the given location.
 46  *
 47  * pointAlongCurveFrom(curve, location, distance)
 48  *
 49  * 	Calculates the coordinates of the point on the given curve that is 'distance' units from location.  'distance' should be in the same coordinate
 50  * space as that used to construct the Bezier curve.  For an HTML Canvas usage, for example, distance would be a measure of pixels.
 51  *
 52  * locationAlongCurveFrom(curve, location, distance)
 53  *
 54  * 	Calculates the location on the given curve that is 'distance' units from location.  'distance' should be in the same coordinate
 55  * space as that used to construct the Bezier curve.  For an HTML Canvas usage, for example, distance would be a measure of pixels.
 56  *
 57  * perpendicularToCurveAt(curve, location, length, distance)
 58  *
 59  * 	Calculates the perpendicular to the given curve at the given location.  length is the length of the line you wish for (it will be centered
 60  * on the point at 'location'). distance is optional, and allows you to specify a point along the path from the given location as the center of
 61  * the perpendicular returned.  The return value of this is an array of two points: [ {x:...,y:...}, {x:...,y:...} ].
 62  *
 63  *
 64  */
 65 
 66 (function() {
 67 
 68     var root = this;
 69 
 70     if(typeof Math.sgn == "undefined") {
 71         Math.sgn = function(x) { return x == 0 ? 0 : x > 0 ? 1 :-1; };
 72     }
 73 
 74     var Vectors = {
 75             subtract 	: 	function(v1, v2) { return {x:v1.x - v2.x, y:v1.y - v2.y }; },
 76             dotProduct	: 	function(v1, v2) { return (v1.x * v2.x)  + (v1.y * v2.y); },
 77             square		:	function(v) { return Math.sqrt((v.x * v.x) + (v.y * v.y)); },
 78             scale		:	function(v, s) { return {x:v.x * s, y:v.y * s }; }
 79         },
 80 
 81         maxRecursion = 64,
 82         flatnessTolerance = Math.pow(2.0,-maxRecursion-1);
 83 
 84     /**
 85      * Calculates the distance that the point lies from the curve.
 86      *
 87      * @param point a point in the form {x:567, y:3342}
 88      * @param curve a Bezier curve in the form [{x:..., y:...}, {x:..., y:...}, {x:..., y:...}, {x:..., y:...}].  note that this is currently
 89      * hardcoded to assume cubiz beziers, but would be better off supporting any degree.
 90      * @return a JS object literal containing location and distance, for example: {location:0.35, distance:10}.  Location is analogous to the location
 91      * argument you pass to the pointOnPath function: it is a ratio of distance travelled along the curve.  Distance is the distance in pixels from
 92      * the point to the curve.
 93      */
 94     var _distanceFromCurve = function(point, curve) {
 95         var candidates = [],
 96             w = _convertToBezier(point, curve),
 97             degree = curve.length - 1, higherDegree = (2 * degree) - 1,
 98             numSolutions = _findRoots(w, higherDegree, candidates, 0),
 99             v = Vectors.subtract(point, curve[0]), dist = Vectors.square(v), t = 0.0;
100 
101         for (var i = 0; i < numSolutions; i++) {
102             v = Vectors.subtract(point, _bezier(curve, degree, candidates[i], null, null));
103             var newDist = Vectors.square(v);
104             if (newDist < dist) {
105                 dist = newDist;
106                 t = candidates[i];
107             }
108         }
109         v = Vectors.subtract(point, curve[degree]);
110         newDist = Vectors.square(v);
111         if (newDist < dist) {
112             dist = newDist;
113             t = 1.0;
114         }
115         return {location:t, distance:dist};
116     };
117     /**
118      * finds the nearest point on the curve to the given point.
119      */
120     var _nearestPointOnCurve = function(point, curve) {
121         var td = _distanceFromCurve(point, curve);
122         return {point:_bezier(curve, curve.length - 1, td.location, null, null), location:td.location};
123     };
124     var _convertToBezier = function(point, curve) {
125         var degree = curve.length - 1, higherDegree = (2 * degree) - 1,
126             c = [], d = [], cdTable = [], w = [],
127             z = [ [1.0, 0.6, 0.3, 0.1], [0.4, 0.6, 0.6, 0.4], [0.1, 0.3, 0.6, 1.0] ];
128 
129         for (var i = 0; i <= degree; i++) c[i] = Vectors.subtract(curve[i], point);
130         for (var i = 0; i <= degree - 1; i++) {
131             d[i] = Vectors.subtract(curve[i+1], curve[i]);
132             d[i] = Vectors.scale(d[i], 3.0);
133         }
134         for (var row = 0; row <= degree - 1; row++) {
135             for (var column = 0; column <= degree; column++) {
136                 if (!cdTable[row]) cdTable[row] = [];
137                 cdTable[row][column] = Vectors.dotProduct(d[row], c[column]);
138             }
139         }
140         for (i = 0; i <= higherDegree; i++) {
141             if (!w[i]) w[i] = [];
142             w[i].y = 0.0;
143             w[i].x = parseFloat(i) / higherDegree;
144         }
145         var n = degree, m = degree-1;
146         for (var k = 0; k <= n + m; k++) {
147             var lb = Math.max(0, k - m),
148                 ub = Math.min(k, n);
149             for (i = lb; i <= ub; i++) {
150                 j = k - i;
151                 w[i+j].y += cdTable[j][i] * z[j][i];
152             }
153         }
154         return w;
155     };
156     /**
157      * counts how many roots there are.
158      */
159     var _findRoots = function(w, degree, t, depth) {
160         var left = [], right = [],
161             left_count, right_count,
162             left_t = [], right_t = [];
163 
164         switch (_getCrossingCount(w, degree)) {
165             case 0 : {
166                 return 0;
167             }
168             case 1 : {
169                 if (depth >= maxRecursion) {
170                     t[0] = (w[0].x + w[degree].x) / 2.0;
171                     return 1;
172                 }
173                 if (_isFlatEnough(w, degree)) {
174                     t[0] = _computeXIntercept(w, degree);
175                     return 1;
176                 }
177                 break;
178             }
179         }
180         _bezier(w, degree, 0.5, left, right);
181         left_count  = _findRoots(left,  degree, left_t, depth+1);
182         right_count = _findRoots(right, degree, right_t, depth+1);
183         for (var i = 0; i < left_count; i++) t[i] = left_t[i];
184         for (var i = 0; i < right_count; i++) t[i+left_count] = right_t[i];
185         return (left_count+right_count);
186     };
187     var _getCrossingCount = function(curve, degree) {
188         var n_crossings = 0, sign, old_sign;
189         sign = old_sign = Math.sgn(curve[0].y);
190         for (var i = 1; i <= degree; i++) {
191             sign = Math.sgn(curve[i].y);
192             if (sign != old_sign) n_crossings++;
193             old_sign = sign;
194         }
195         return n_crossings;
196     };
197     var _isFlatEnough = function(curve, degree) {
198         var  error,
199             intercept_1, intercept_2, left_intercept, right_intercept,
200             a, b, c, det, dInv, a1, b1, c1, a2, b2, c2;
201         a = curve[0].y - curve[degree].y;
202         b = curve[degree].x - curve[0].x;
203         c = curve[0].x * curve[degree].y - curve[degree].x * curve[0].y;
204 
205         var max_distance_above = max_distance_below = 0.0;
206 
207         for (var i = 1; i < degree; i++) {
208             var value = a * curve[i].x + b * curve[i].y + c;
209             if (value > max_distance_above)
210                 max_distance_above = value;
211             else if (value < max_distance_below)
212                 max_distance_below = value;
213         }
214 
215         a1 = 0.0; b1 = 1.0; c1 = 0.0; a2 = a; b2 = b;
216         c2 = c - max_distance_above;
217         det = a1 * b2 - a2 * b1;
218         dInv = 1.0/det;
219         intercept_1 = (b1 * c2 - b2 * c1) * dInv;
220         a2 = a; b2 = b; c2 = c - max_distance_below;
221         det = a1 * b2 - a2 * b1;
222         dInv = 1.0/det;
223         intercept_2 = (b1 * c2 - b2 * c1) * dInv;
224         left_intercept = Math.min(intercept_1, intercept_2);
225         right_intercept = Math.max(intercept_1, intercept_2);
226         error = right_intercept - left_intercept;
227         return (error < flatnessTolerance)? 1 : 0;
228     };
229     var _computeXIntercept = function(curve, degree) {
230         var XLK = 1.0, YLK = 0.0,
231             XNM = curve[degree].x - curve[0].x, YNM = curve[degree].y - curve[0].y,
232             XMK = curve[0].x - 0.0, YMK = curve[0].y - 0.0,
233             det = XNM*YLK - YNM*XLK, detInv = 1.0/det,
234             S = (XNM*YMK - YNM*XMK) * detInv;
235         return 0.0 + XLK * S;
236     };
237     var _bezier = function(curve, degree, t, left, right) {
238         var temp = [[]];
239         for (var j =0; j <= degree; j++) temp[0][j] = curve[j];
240         for (var i = 1; i <= degree; i++) {
241             for (var j =0 ; j <= degree - i; j++) {
242                 if (!temp[i]) temp[i] = [];
243                 if (!temp[i][j]) temp[i][j] = {};
244                 temp[i][j].x = (1.0 - t) * temp[i-1][j].x + t * temp[i-1][j+1].x;
245                 temp[i][j].y = (1.0 - t) * temp[i-1][j].y + t * temp[i-1][j+1].y;
246             }
247         }
248         if (left != null)
249             for (j = 0; j <= degree; j++) left[j]  = temp[j][0];
250         if (right != null)
251             for (j = 0; j <= degree; j++) right[j] = temp[degree-j][j];
252 
253         return (temp[degree][0]);
254     };
255 
256     var _curveFunctionCache = {};
257     var _getCurveFunctions = function(order) {
258         var fns = _curveFunctionCache[order];
259         if (!fns) {
260             fns = [];
261             var f_term = function() { return function(t) { return Math.pow(t, order); }; },
262                 l_term = function() { return function(t) { return Math.pow((1-t), order); }; },
263                 c_term = function(c) { return function(t) { return c; }; },
264                 t_term = function() { return function(t) { return t; }; },
265                 one_minus_t_term = function() { return function(t) { return 1-t; }; },
266                 _termFunc = function(terms) {
267                     return function(t) {
268                         var p = 1;
269                         for (var i = 0; i < terms.length; i++) p = p * terms[i](t);
270                         return p;
271                     };
272                 };
273 
274             fns.push(new f_term());  // first is t to the power of the curve order
275             for (var i = 1; i < order; i++) {
276                 var terms = [new c_term(order)];
277                 for (var j = 0 ; j < (order - i); j++) terms.push(new t_term());
278                 for (var j = 0 ; j < i; j++) terms.push(new one_minus_t_term());
279                 fns.push(new _termFunc(terms));
280             }
281             fns.push(new l_term());  // last is (1-t) to the power of the curve order
282 
283             _curveFunctionCache[order] = fns;
284         }
285 
286         return fns;
287     };
288 
289 
290     /**
291      * calculates a point on the curve, for a Bezier of arbitrary order.
292      * @param curve an array of control points, eg [{x:10,y:20}, {x:50,y:50}, {x:100,y:100}, {x:120,y:100}].  For a cubic bezier this should have four points.
293      * @param location a decimal indicating the distance along the curve the point should be located at.  this is the distance along the curve as it travels, taking the way it bends into account.  should be a number from 0 to 1, inclusive.
294      */
295     var _pointOnPath = function(curve, location) {
296         var cc = _getCurveFunctions(curve.length - 1),
297             _x = 0, _y = 0;
298         for (var i = 0; i < curve.length ; i++) {
299             _x = _x + (curve[i].x * cc[i](location));
300             _y = _y + (curve[i].y * cc[i](location));
301         }
302 
303         return {x:_x, y:_y};
304     };
305 
306     var _dist = function(p1,p2) {
307         return Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2));
308     };
309 
310     var _isPoint = function(curve) {
311         return curve[0].x == curve[1].x && curve[0].y == curve[1].y;
312     };
313 
314     /**
315      * finds the point that is 'distance' along the path from 'location'.  this method returns both the x,y location of the point and also
316      * its 'location' (proportion of travel along the path); the method below - _pointAlongPathFrom - calls this method and just returns the
317      * point.
318      */
319     var _pointAlongPath = function(curve, location, distance) {
320 
321         if (_isPoint(curve)) {
322             return {
323                 point:curve[0],
324                 location:location
325             };
326         }
327 
328         var prev = _pointOnPath(curve, location),
329             tally = 0,
330             curLoc = location,
331             direction = distance > 0 ? 1 : -1,
332             cur = null;
333 
334         while (tally < Math.abs(distance)) {
335             curLoc += (0.005 * direction);
336             cur = _pointOnPath(curve, curLoc);
337             tally += _dist(cur, prev);
338             prev = cur;
339         }
340         return {point:cur, location:curLoc};
341     };
342 
343     var _length = function(curve) {
344         if (_isPoint(curve)) return 0;
345 
346         var prev = _pointOnPath(curve, 0),
347             tally = 0,
348             curLoc = 0,
349             direction = 1,
350             cur = null;
351 
352         while (curLoc < 1) {
353             curLoc += (0.005 * direction);
354             cur = _pointOnPath(curve, curLoc);
355             tally += _dist(cur, prev);
356             prev = cur;
357         }
358         return tally;
359     };
360 
361     /**
362      * finds the point that is 'distance' along the path from 'location'.
363      */
364     var _pointAlongPathFrom = function(curve, location, distance) {
365         return _pointAlongPath(curve, location, distance).point;
366     };
367 
368     /**
369      * finds the location that is 'distance' along the path from 'location'.
370      */
371     var _locationAlongPathFrom = function(curve, location, distance) {
372         return _pointAlongPath(curve, location, distance).location;
373     };
374 
375     /**
376      * returns the gradient of the curve at the given location, which is a decimal between 0 and 1 inclusive.
377      *
378      * thanks // http://bimixual.org/AnimationLibrary/beziertangents.html
379      */
380     var _gradientAtPoint = function(curve, location) {
381         var p1 = _pointOnPath(curve, location),
382             p2 = _pointOnPath(curve.slice(0, curve.length - 1), location),
383             dy = p2.y - p1.y, dx = p2.x - p1.x;
384         return dy == 0 ? Infinity : Math.atan(dy / dx);
385     };
386 
387     /**
388      returns the gradient of the curve at the point which is 'distance' from the given location.
389      if this point is greater than location 1, the gradient at location 1 is returned.
390      if this point is less than location 0, the gradient at location 0 is returned.
391      */
392     var _gradientAtPointAlongPathFrom = function(curve, location, distance) {
393         var p = _pointAlongPath(curve, location, distance);
394         if (p.location > 1) p.location = 1;
395         if (p.location < 0) p.location = 0;
396         return _gradientAtPoint(curve, p.location);
397     };
398 
399     /**
400      * calculates a line that is 'length' pixels long, perpendicular to, and centered on, the path at 'distance' pixels from the given location.
401      * if distance is not supplied, the perpendicular for the given location is computed (ie. we set distance to zero).
402      */
403     var _perpendicularToPathAt = function(curve, location, length, distance) {
404         distance = distance == null ? 0 : distance;
405         var p = _pointAlongPath(curve, location, distance),
406             m = _gradientAtPoint(curve, p.location),
407             _theta2 = Math.atan(-1 / m),
408             y =  length / 2 * Math.sin(_theta2),
409             x =  length / 2 * Math.cos(_theta2);
410         return [{x:p.point.x + x, y:p.point.y + y}, {x:p.point.x - x, y:p.point.y - y}];
411     };
412 
413     var jsBezier = this.jsBezier = {
414         distanceFromCurve : _distanceFromCurve,
415         gradientAtPoint : _gradientAtPoint,
416         gradientAtPointAlongCurveFrom : _gradientAtPointAlongPathFrom,
417         nearestPointOnCurve : _nearestPointOnCurve,
418         pointOnCurve : _pointOnPath,
419         pointAlongCurveFrom : _pointAlongPathFrom,
420         perpendicularToCurveAt : _perpendicularToPathAt,
421         locationAlongCurveFrom:_locationAlongPathFrom,
422         getLength:_length,
423         version:"0.9.0"
424     };
425 
426     if (typeof exports !== "undefined") {
427         exports.jsBezier = jsBezier;
428     }
429 
430 }).call(typeof window !== 'undefined' ? window : this);
431 
432 /**
433  * Biltong v0.4.0
434  *
435  * Various geometry functions written as part of jsPlumb and perhaps useful for others.
436  *
437  * Copyright (c) 2017 jsPlumb
438  * https://jsplumbtoolkit.com
439  *
440  * Permission is hereby granted, free of charge, to any person
441  * obtaining a copy of this software and associated documentation
442  * files (the "Software"), to deal in the Software without
443  * restriction, including without limitation the rights to use,
444  * copy, modify, merge, publish, distribute, sublicense, and/or sell
445  * copies of the Software, and to permit persons to whom the
446  * Software is furnished to do so, subject to the following
447  * conditions:
448  *
449  * The above copyright notice and this permission notice shall be
450  * included in all copies or substantial portions of the Software.
451  *
452  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
453  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
454  * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
455  * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
456  * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
457  * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
458  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
459  * OTHER DEALINGS IN THE SOFTWARE.
460  */
461 ;(function() {
462 
463     "use strict";
464     var root = this;
465 
466     var Biltong = root.Biltong = {
467         version:"0.4.0"
468     };
469 
470     if (typeof exports !== "undefined") {
471         exports.Biltong = Biltong;
472     }
473 
474     var _isa = function(a) { return Object.prototype.toString.call(a) === "[object Array]"; },
475         _pointHelper = function(p1, p2, fn) {
476             p1 = _isa(p1) ? p1 : [p1.x, p1.y];
477             p2 = _isa(p2) ? p2 : [p2.x, p2.y];
478             return fn(p1, p2);
479         },
480         /**
481          * @name Biltong.gradient
482          * @function
483          * @desc Calculates the gradient of a line between the two points.
484          * @param {Point} p1 First point, either as a 2 entry array or object with `left` and `top` properties.
485          * @param {Point} p2 Second point, either as a 2 entry array or object with `left` and `top` properties.
486          * @return {Float} The gradient of a line between the two points.
487          */
488         _gradient = Biltong.gradient = function(p1, p2) {
489             return _pointHelper(p1, p2, function(_p1, _p2) {
490                 if (_p2[0] == _p1[0])
491                     return _p2[1] > _p1[1] ? Infinity : -Infinity;
492                 else if (_p2[1] == _p1[1])
493                     return _p2[0] > _p1[0] ? 0 : -0;
494                 else
495                     return (_p2[1] - _p1[1]) / (_p2[0] - _p1[0]);
496             });
497         },
498         /**
499          * @name Biltong.normal
500          * @function
501          * @desc Calculates the gradient of a normal to a line between the two points.
502          * @param {Point} p1 First point, either as a 2 entry array or object with `left` and `top` properties.
503          * @param {Point} p2 Second point, either as a 2 entry array or object with `left` and `top` properties.
504          * @return {Float} The gradient of a normal to a line between the two points.
505          */
506         _normal = Biltong.normal = function(p1, p2) {
507             return -1 / _gradient(p1, p2);
508         },
509         /**
510          * @name Biltong.lineLength
511          * @function
512          * @desc Calculates the length of a line between the two points.
513          * @param {Point} p1 First point, either as a 2 entry array or object with `left` and `top` properties.
514          * @param {Point} p2 Second point, either as a 2 entry array or object with `left` and `top` properties.
515          * @return {Float} The length of a line between the two points.
516          */
517         _lineLength = Biltong.lineLength = function(p1, p2) {
518             return _pointHelper(p1, p2, function(_p1, _p2) {
519                 return Math.sqrt(Math.pow(_p2[1] - _p1[1], 2) + Math.pow(_p2[0] - _p1[0], 2));
520             });
521         },
522         /**
523          * @name Biltong.quadrant
524          * @function
525          * @desc Calculates the quadrant in which the angle between the two points lies.
526          * @param {Point} p1 First point, either as a 2 entry array or object with `left` and `top` properties.
527          * @param {Point} p2 Second point, either as a 2 entry array or object with `left` and `top` properties.
528          * @return {Integer} The quadrant - 1 for upper right, 2 for lower right, 3 for lower left, 4 for upper left.
529          */
530         _quadrant = Biltong.quadrant = function(p1, p2) {
531             return _pointHelper(p1, p2, function(_p1, _p2) {
532                 if (_p2[0] > _p1[0]) {
533                     return (_p2[1] > _p1[1]) ? 2 : 1;
534                 }
535                 else if (_p2[0] == _p1[0]) {
536                     return _p2[1] > _p1[1] ? 2 : 1;
537                 }
538                 else {
539                     return (_p2[1] > _p1[1]) ? 3 : 4;
540                 }
541             });
542         },
543         /**
544          * @name Biltong.theta
545          * @function
546          * @desc Calculates the angle between the two points.
547          * @param {Point} p1 First point, either as a 2 entry array or object with `left` and `top` properties.
548          * @param {Point} p2 Second point, either as a 2 entry array or object with `left` and `top` properties.
549          * @return {Float} The angle between the two points.
550          */
551         _theta = Biltong.theta = function(p1, p2) {
552             return _pointHelper(p1, p2, function(_p1, _p2) {
553                 var m = _gradient(_p1, _p2),
554                     t = Math.atan(m),
555                     s = _quadrant(_p1, _p2);
556                 if ((s == 4 || s== 3)) t += Math.PI;
557                 if (t < 0) t += (2 * Math.PI);
558 
559                 return t;
560             });
561         },
562         /**
563          * @name Biltong.intersects
564          * @function
565          * @desc Calculates whether or not the two rectangles intersect.
566          * @param {Rectangle} r1 First rectangle, as a js object in the form `{x:.., y:.., w:.., h:..}`
567          * @param {Rectangle} r2 Second rectangle, as a js object in the form `{x:.., y:.., w:.., h:..}`
568          * @return {Boolean} True if the rectangles intersect, false otherwise.
569          */
570         _intersects = Biltong.intersects = function(r1, r2) {
571             var x1 = r1.x, x2 = r1.x + r1.w, y1 = r1.y, y2 = r1.y + r1.h,
572                 a1 = r2.x, a2 = r2.x + r2.w, b1 = r2.y, b2 = r2.y + r2.h;
573 
574             return  ( (x1 <= a1 && a1 <= x2) && (y1 <= b1 && b1 <= y2) ) ||
575                 ( (x1 <= a2 && a2 <= x2) && (y1 <= b1 && b1 <= y2) ) ||
576                 ( (x1 <= a1 && a1 <= x2) && (y1 <= b2 && b2 <= y2) ) ||
577                 ( (x1 <= a2 && a1 <= x2) && (y1 <= b2 && b2 <= y2) ) ||
578                 ( (a1 <= x1 && x1 <= a2) && (b1 <= y1 && y1 <= b2) ) ||
579                 ( (a1 <= x2 && x2 <= a2) && (b1 <= y1 && y1 <= b2) ) ||
580                 ( (a1 <= x1 && x1 <= a2) && (b1 <= y2 && y2 <= b2) ) ||
581                 ( (a1 <= x2 && x1 <= a2) && (b1 <= y2 && y2 <= b2) );
582         },
583         /**
584          * @name Biltong.encloses
585          * @function
586          * @desc Calculates whether or not r2 is completely enclosed by r1.
587          * @param {Rectangle} r1 First rectangle, as a js object in the form `{x:.., y:.., w:.., h:..}`
588          * @param {Rectangle} r2 Second rectangle, as a js object in the form `{x:.., y:.., w:.., h:..}`
589          * @param {Boolean} [allowSharedEdges=false] If true, the concept of enclosure allows for one or more edges to be shared by the two rectangles.
590          * @return {Boolean} True if r1 encloses r2, false otherwise.
591          */
592         _encloses = Biltong.encloses = function(r1, r2, allowSharedEdges) {
593             var x1 = r1.x, x2 = r1.x + r1.w, y1 = r1.y, y2 = r1.y + r1.h,
594                 a1 = r2.x, a2 = r2.x + r2.w, b1 = r2.y, b2 = r2.y + r2.h,
595                 c = function(v1, v2, v3, v4) { return allowSharedEdges ? v1 <= v2 && v3>= v4 : v1 < v2 && v3 > v4; };
596 
597             return c(x1,a1,x2,a2) && c(y1,b1,y2,b2);
598         },
599         _segmentMultipliers = [null, [1, -1], [1, 1], [-1, 1], [-1, -1] ],
600         _inverseSegmentMultipliers = [null, [-1, -1], [-1, 1], [1, 1], [1, -1] ],
601         /**
602          * @name Biltong.pointOnLine
603          * @function
604          * @desc Calculates a point on the line from `fromPoint` to `toPoint` that is `distance` units along the length of the line.
605          * @param {Point} p1 First point, either as a 2 entry array or object with `left` and `top` properties.
606          * @param {Point} p2 Second point, either as a 2 entry array or object with `left` and `top` properties.
607          * @return {Point} Point on the line, in the form `{ x:..., y:... }`.
608          */
609         _pointOnLine = Biltong.pointOnLine = function(fromPoint, toPoint, distance) {
610             var m = _gradient(fromPoint, toPoint),
611                 s = _quadrant(fromPoint, toPoint),
612                 segmentMultiplier = distance > 0 ? _segmentMultipliers[s] : _inverseSegmentMultipliers[s],
613                 theta = Math.atan(m),
614                 y = Math.abs(distance * Math.sin(theta)) * segmentMultiplier[1],
615                 x =  Math.abs(distance * Math.cos(theta)) * segmentMultiplier[0];
616             return { x:fromPoint.x + x, y:fromPoint.y + y };
617         },
618         /**
619          * @name Biltong.perpendicularLineTo
620          * @function
621          * @desc Calculates a line of length `length` that is perpendicular to the line from `fromPoint` to `toPoint` and passes through `toPoint`.
622          * @param {Point} p1 First point, either as a 2 entry array or object with `left` and `top` properties.
623          * @param {Point} p2 Second point, either as a 2 entry array or object with `left` and `top` properties.
624          * @return {Line} Perpendicular line, in the form `[ { x:..., y:... }, { x:..., y:... } ]`.
625          */
626         _perpendicularLineTo = Biltong.perpendicularLineTo = function(fromPoint, toPoint, length) {
627             var m = _gradient(fromPoint, toPoint),
628                 theta2 = Math.atan(-1 / m),
629                 y =  length / 2 * Math.sin(theta2),
630                 x =  length / 2 * Math.cos(theta2);
631             return [{x:toPoint.x + x, y:toPoint.y + y}, {x:toPoint.x - x, y:toPoint.y - y}];
632         };
633 }).call(typeof window !== 'undefined' ? window : this);
634 ;
635 (function () {
636 
637     "use strict";
638 
639     var root = this,
640         Sniff = {
641             android: navigator.userAgent.toLowerCase().indexOf("android") > -1
642         },
643         matchesSelector = function (el, selector, ctx) {
644             ctx = ctx || el.parentNode;
645             var possibles = ctx.querySelectorAll(selector);
646             for (var i = 0; i < possibles.length; i++) {
647                 if (possibles[i] === el) {
648                     return true;
649                 }
650             }
651             return false;
652         },
653         _gel = function (el) {
654             return (typeof el == "string" || el.constructor === String) ? document.getElementById(el) : el;
655         },
656         _t = function (e) {
657             return e.srcElement || e.target;
658         },
659     //
660     // gets path info for the given event - the path from target to obj, in the event's bubble chain. if doCompute
661     // is false we just return target for the path.
662     //
663         _pi = function(e, target, obj, doCompute) {
664             if (!doCompute) return { path:[target], end:1 };
665             else if (typeof e.path !== "undefined" && e.path.indexOf) {
666                 return { path: e.path, end: e.path.indexOf(obj) };
667             } else {
668                 var out = { path:[], end:-1 }, _one = function(el) {
669                     out.path.push(el);
670                     if (el === obj) {
671                         out.end = out.path.length - 1;
672                     }
673                     else if (el.parentNode != null) {
674                         _one(el.parentNode)
675                     }
676                 };
677                 _one(target);
678                 return out;
679             }
680         },
681         _d = function (l, fn) {
682             for (var i = 0, j = l.length; i < j; i++) {
683                 if (l[i] == fn) break;
684             }
685             if (i < l.length) l.splice(i, 1);
686         },
687         guid = 1,
688     //
689     // this function generates a guid for every handler, sets it on the handler, then adds
690     // it to the associated object's map of handlers for the given event. this is what enables us
691     // to unbind all events of some type, or all events (the second of which can be requested by the user,
692     // but it also used by Mottle when an element is removed.)
693         _store = function (obj, event, fn) {
694             var g = guid++;
695             obj.__ta = obj.__ta || {};
696             obj.__ta[event] = obj.__ta[event] || {};
697             // store each handler with a unique guid.
698             obj.__ta[event][g] = fn;
699             // set the guid on the handler.
700             fn.__tauid = g;
701             return g;
702         },
703         _unstore = function (obj, event, fn) {
704             obj.__ta && obj.__ta[event] && delete obj.__ta[event][fn.__tauid];
705             // a handler might have attached extra functions, so we unbind those too.
706             if (fn.__taExtra) {
707                 for (var i = 0; i < fn.__taExtra.length; i++) {
708                     _unbind(obj, fn.__taExtra[i][0], fn.__taExtra[i][1]);
709                 }
710                 fn.__taExtra.length = 0;
711             }
712             // a handler might have attached an unstore callback
713             fn.__taUnstore && fn.__taUnstore();
714         },
715         _curryChildFilter = function (children, obj, fn, evt) {
716             if (children == null) return fn;
717             else {
718                 var c = children.split(","),
719                     _fn = function (e) {
720                         _fn.__tauid = fn.__tauid;
721                         var t = _t(e), target = t;  // t is the target element on which the event occurred. it is the
722                         // element we will wish to pass to any callbacks.
723                         var pathInfo = _pi(e, t, obj, children != null)
724                         if (pathInfo.end != -1) {
725                             for (var p = 0; p < pathInfo.end; p++) {
726                                 target = pathInfo.path[p];
727                                 for (var i = 0; i < c.length; i++) {
728                                     if (matchesSelector(target, c[i], obj)) {
729                                         fn.apply(target, arguments);
730                                     }
731                                 }
732                             }
733                         }
734                     };
735                 registerExtraFunction(fn, evt, _fn);
736                 return _fn;
737             }
738         },
739     //
740     // registers an 'extra' function on some event listener function we were given - a function that we
741     // created and bound to the element as part of our housekeeping, and which we want to unbind and remove
742     // whenever the given function is unbound.
743         registerExtraFunction = function (fn, evt, newFn) {
744             fn.__taExtra = fn.__taExtra || [];
745             fn.__taExtra.push([evt, newFn]);
746         },
747         DefaultHandler = function (obj, evt, fn, children) {
748             if (isTouchDevice && touchMap[evt]) {
749                 var tfn = _curryChildFilter(children, obj, fn, touchMap[evt]);
750                 _bind(obj, touchMap[evt], tfn , fn);
751             }
752             if (evt === "focus" && obj.getAttribute("tabindex") == null) {
753                 obj.setAttribute("tabindex", "1");
754             }
755             _bind(obj, evt, _curryChildFilter(children, obj, fn, evt), fn);
756         },
757         SmartClickHandler = function (obj, evt, fn, children) {
758             if (obj.__taSmartClicks == null) {
759                 var down = function (e) {
760                         obj.__tad = _pageLocation(e);
761                     },
762                     up = function (e) {
763                         obj.__tau = _pageLocation(e);
764                     },
765                     click = function (e) {
766                         if (obj.__tad && obj.__tau && obj.__tad[0] === obj.__tau[0] && obj.__tad[1] === obj.__tau[1]) {
767                             for (var i = 0; i < obj.__taSmartClicks.length; i++)
768                                 obj.__taSmartClicks[i].apply(_t(e), [ e ]);
769                         }
770                     };
771                 DefaultHandler(obj, "mousedown", down, children);
772                 DefaultHandler(obj, "mouseup", up, children);
773                 DefaultHandler(obj, "click", click, children);
774                 obj.__taSmartClicks = [];
775             }
776 
777             // store in the list of callbacks
778             obj.__taSmartClicks.push(fn);
779             // the unstore function removes this function from the object's listener list for this type.
780             fn.__taUnstore = function () {
781                 _d(obj.__taSmartClicks, fn);
782             };
783         },
784         _tapProfiles = {
785             "tap": {touches: 1, taps: 1},
786             "dbltap": {touches: 1, taps: 2},
787             "contextmenu": {touches: 2, taps: 1}
788         },
789         TapHandler = function (clickThreshold, dblClickThreshold) {
790             return function (obj, evt, fn, children) {
791                 // if event is contextmenu, for devices which are mouse only, we want to
792                 // use the default bind.
793                 if (evt == "contextmenu" && isMouseDevice)
794                     DefaultHandler(obj, evt, fn, children);
795                 else {
796                     // the issue here is that this down handler gets registered only for the
797                     // child nodes in the first registration. in fact it should be registered with
798                     // no child selector and then on down we should cycle through the registered
799                     // functions to see if one of them matches. on mouseup we should execute ALL of
800                     // the functions whose children are either null or match the element.
801                     if (obj.__taTapHandler == null) {
802                         var tt = obj.__taTapHandler = {
803                             tap: [],
804                             dbltap: [],
805                             contextmenu: [],
806                             down: false,
807                             taps: 0,
808                             downSelectors: []
809                         };
810                         var down = function (e) {
811                                 var target = _t(e), pathInfo = _pi(e, target, obj, children != null), finished = false;
812                                 for (var p = 0; p < pathInfo.end; p++) {
813                                     if (finished) return;
814                                     target = pathInfo.path[p];
815                                     for (var i = 0; i < tt.downSelectors.length; i++) {
816                                         if (tt.downSelectors[i] == null || matchesSelector(target, tt.downSelectors[i], obj)) {
817                                             tt.down = true;
818                                             setTimeout(clearSingle, clickThreshold);
819                                             setTimeout(clearDouble, dblClickThreshold);
820                                             finished = true;
821                                             break; // we only need one match on mousedown
822                                         }
823                                     }
824                                 }
825                             },
826                             up = function (e) {
827                                 if (tt.down) {
828                                     var target = _t(e), currentTarget, pathInfo;
829                                     tt.taps++;
830                                     var tc = _touchCount(e);
831                                     for (var eventId in _tapProfiles) {
832                                         if (_tapProfiles.hasOwnProperty(eventId)) {
833                                             var p = _tapProfiles[eventId];
834                                             if (p.touches === tc && (p.taps === 1 || p.taps === tt.taps)) {
835                                                 for (var i = 0; i < tt[eventId].length; i++) {
836                                                     pathInfo = _pi(e, target, obj, tt[eventId][i][1] != null);
837                                                     for (var pLoop = 0; pLoop < pathInfo.end; pLoop++) {
838                                                         currentTarget = pathInfo.path[pLoop];
839                                                         // this is a single event registration handler.
840                                                         if (tt[eventId][i][1] == null || matchesSelector(currentTarget, tt[eventId][i][1], obj)) {
841                                                             tt[eventId][i][0].apply(currentTarget, [ e ]);
842                                                             break;
843                                                         }
844                                                     }
845                                                 }
846                                             }
847                                         }
848                                     }
849                                 }
850                             },
851                             clearSingle = function () {
852                                 tt.down = false;
853                             },
854                             clearDouble = function () {
855                                 tt.taps = 0;
856                             };
857 
858                         DefaultHandler(obj, "mousedown", down);
859                         DefaultHandler(obj, "mouseup", up);
860                     }
861                     // add this child selector (it can be null, that's fine).
862                     obj.__taTapHandler.downSelectors.push(children);
863 
864                     obj.__taTapHandler[evt].push([fn, children]);
865                     // the unstore function removes this function from the object's listener list for this type.
866                     fn.__taUnstore = function () {
867                         _d(obj.__taTapHandler[evt], fn);
868                     };
869                 }
870             };
871         },
872         meeHelper = function (type, evt, obj, target) {
873             for (var i in obj.__tamee[type]) {
874                 if (obj.__tamee[type].hasOwnProperty(i)) {
875                     obj.__tamee[type][i].apply(target, [ evt ]);
876                 }
877             }
878         },
879         MouseEnterExitHandler = function () {
880             var activeElements = [];
881             return function (obj, evt, fn, children) {
882                 if (!obj.__tamee) {
883                     // __tamee holds a flag saying whether the mouse is currently "in" the element, and a list of
884                     // both mouseenter and mouseexit functions.
885                     obj.__tamee = { over: false, mouseenter: [], mouseexit: [] };
886                     // register over and out functions
887                     var over = function (e) {
888                             var t = _t(e);
889                             if ((children == null && (t == obj && !obj.__tamee.over)) || (matchesSelector(t, children, obj) && (t.__tamee == null || !t.__tamee.over))) {
890                                 meeHelper("mouseenter", e, obj, t);
891                                 t.__tamee = t.__tamee || {};
892                                 t.__tamee.over = true;
893                                 activeElements.push(t);
894                             }
895                         },
896                         out = function (e) {
897                             var t = _t(e);
898                             // is the current target one of the activeElements? and is the
899                             // related target NOT a descendant of it?
900                             for (var i = 0; i < activeElements.length; i++) {
901                                 if (t == activeElements[i] && !matchesSelector((e.relatedTarget || e.toElement), "*", t)) {
902                                     t.__tamee.over = false;
903                                     activeElements.splice(i, 1);
904                                     meeHelper("mouseexit", e, obj, t);
905                                 }
906                             }
907                         };
908 
909                     _bind(obj, "mouseover", _curryChildFilter(children, obj, over, "mouseover"), over);
910                     _bind(obj, "mouseout", _curryChildFilter(children, obj, out, "mouseout"), out);
911                 }
912 
913                 fn.__taUnstore = function () {
914                     delete obj.__tamee[evt][fn.__tauid];
915                 };
916 
917                 _store(obj, evt, fn);
918                 obj.__tamee[evt][fn.__tauid] = fn;
919             };
920         },
921         isTouchDevice = "ontouchstart" in document.documentElement,
922         isMouseDevice = "onmousedown" in document.documentElement,
923         touchMap = { "mousedown": "touchstart", "mouseup": "touchend", "mousemove": "touchmove" },
924         touchstart = "touchstart", touchend = "touchend", touchmove = "touchmove",
925         iev = (function () {
926             var rv = -1;
927             if (navigator.appName == 'Microsoft Internet Explorer') {
928                 var ua = navigator.userAgent,
929                     re = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})");
930                 if (re.exec(ua) != null)
931                     rv = parseFloat(RegExp.$1);
932             }
933             return rv;
934         })(),
935         isIELT9 = iev > -1 && iev < 9,
936         _genLoc = function (e, prefix) {
937             if (e == null) return [ 0, 0 ];
938             var ts = _touches(e), t = _getTouch(ts, 0);
939             return [t[prefix + "X"], t[prefix + "Y"]];
940         },
941         _pageLocation = function (e) {
942             if (e == null) return [ 0, 0 ];
943             if (isIELT9) {
944                 return [ e.clientX + document.documentElement.scrollLeft, e.clientY + document.documentElement.scrollTop ];
945             }
946             else {
947                 return _genLoc(e, "page");
948             }
949         },
950         _screenLocation = function (e) {
951             return _genLoc(e, "screen");
952         },
953         _clientLocation = function (e) {
954             return _genLoc(e, "client");
955         },
956         _getTouch = function (touches, idx) {
957             return touches.item ? touches.item(idx) : touches[idx];
958         },
959         _touches = function (e) {
960             return e.touches && e.touches.length > 0 ? e.touches :
961                     e.changedTouches && e.changedTouches.length > 0 ? e.changedTouches :
962                     e.targetTouches && e.targetTouches.length > 0 ? e.targetTouches :
963                 [ e ];
964         },
965         _touchCount = function (e) {
966             return _touches(e).length;
967         },
968     //http://www.quirksmode.org/blog/archives/2005/10/_and_the_winner_1.html
969         _bind = function (obj, type, fn, originalFn) {
970             _store(obj, type, fn);
971             originalFn.__tauid = fn.__tauid;
972             if (obj.addEventListener)
973                 obj.addEventListener(type, fn, false);
974             else if (obj.attachEvent) {
975                 var key = type + fn.__tauid;
976                 obj["e" + key] = fn;
977                 // TODO look at replacing with .call(..)
978                 obj[key] = function () {
979                     obj["e" + key] && obj["e" + key](window.event);
980                 };
981                 obj.attachEvent("on" + type, obj[key]);
982             }
983         },
984         _unbind = function (obj, type, fn) {
985             if (fn == null) return;
986             _each(obj, function () {
987                 var _el = _gel(this);
988                 _unstore(_el, type, fn);
989                 // it has been bound if there is a tauid. otherwise it was not bound and we can ignore it.
990                 if (fn.__tauid != null) {
991                     if (_el.removeEventListener) {
992                         _el.removeEventListener(type, fn, false);
993                         if (isTouchDevice && touchMap[type]) _el.removeEventListener(touchMap[type], fn, false);
994                     }
995                     else if (this.detachEvent) {
996                         var key = type + fn.__tauid;
997                         _el[key] && _el.detachEvent("on" + type, _el[key]);
998                         _el[key] = null;
999                         _el["e" + key] = null;
1000                     }
1001                 }
1002 
1003                 // if a touch event was also registered, deregister now.
1004                 if (fn.__taTouchProxy) {
1005                     _unbind(obj, fn.__taTouchProxy[1], fn.__taTouchProxy[0]);
1006                 }
1007             });
1008         },
1009         _each = function (obj, fn) {
1010             if (obj == null) return;
1011             // if a list (or list-like), use it. if a string, get a list
1012             // by running the string through querySelectorAll. else, assume
1013             // it's an Element.
1014             // obj.top is "unknown" in IE8.
1015             obj = (typeof Window !== "undefined" && (typeof obj.top !== "unknown" && obj == obj.top)) ? [ obj ] :
1016                     (typeof obj !== "string") && (obj.tagName == null && obj.length != null) ? obj :
1017                     typeof obj === "string" ? document.querySelectorAll(obj)
1018                 : [ obj ];
1019 
1020             for (var i = 0; i < obj.length; i++)
1021                 fn.apply(obj[i]);
1022         };
1023 
1024     /**
1025      * Mottle offers support for abstracting out the differences
1026      * between touch and mouse devices, plus "smart click" functionality
1027      * (don't fire click if the mouse has moved between mousedown and mouseup),
1028      * and synthesized click/tap events.
1029      * @class Mottle
1030      * @constructor
1031      * @param {Object} params Constructor params
1032      * @param {Number} [params.clickThreshold=250] Threshold, in milliseconds beyond which a touchstart followed by a touchend is not considered to be a click.
1033      * @param {Number} [params.dblClickThreshold=450] Threshold, in milliseconds beyond which two successive tap events are not considered to be a click.
1034      * @param {Boolean} [params.smartClicks=false] If true, won't fire click events if the mouse has moved between mousedown and mouseup. Note that this functionality
1035      * requires that Mottle consume the mousedown event, and so may not be viable in all use cases.
1036      */
1037     root.Mottle = function (params) {
1038         params = params || {};
1039         var clickThreshold = params.clickThreshold || 250,
1040             dblClickThreshold = params.dblClickThreshold || 450,
1041             mouseEnterExitHandler = new MouseEnterExitHandler(),
1042             tapHandler = new TapHandler(clickThreshold, dblClickThreshold),
1043             _smartClicks = params.smartClicks,
1044             _doBind = function (obj, evt, fn, children) {
1045                 if (fn == null) return;
1046                 _each(obj, function () {
1047                     var _el = _gel(this);
1048                     if (_smartClicks && evt === "click")
1049                         SmartClickHandler(_el, evt, fn, children);
1050                     else if (evt === "tap" || evt === "dbltap" || evt === "contextmenu") {
1051                         tapHandler(_el, evt, fn, children);
1052                     }
1053                     else if (evt === "mouseenter" || evt == "mouseexit")
1054                         mouseEnterExitHandler(_el, evt, fn, children);
1055                     else
1056                         DefaultHandler(_el, evt, fn, children);
1057                 });
1058             };
1059 
1060         /**
1061          * Removes an element from the DOM, and deregisters all event handlers for it. You should use this
1062          * to ensure you don't leak memory.
1063          * @method remove
1064          * @param {String|Element} el Element, or id of the element, to remove.
1065          * @return {Mottle} The current Mottle instance; you can chain this method.
1066          */
1067         this.remove = function (el) {
1068             _each(el, function () {
1069                 var _el = _gel(this);
1070                 if (_el.__ta) {
1071                     for (var evt in _el.__ta) {
1072                         if (_el.__ta.hasOwnProperty(evt)) {
1073                             for (var h in _el.__ta[evt]) {
1074                                 if (_el.__ta[evt].hasOwnProperty(h))
1075                                     _unbind(_el, evt, _el.__ta[evt][h]);
1076                             }
1077                         }
1078                     }
1079                 }
1080                 _el.parentNode && _el.parentNode.removeChild(_el);
1081             });
1082             return this;
1083         };
1084 
1085         /**
1086          * Register an event handler, optionally as a delegate for some set of descendant elements. Note
1087          * that this method takes either 3 or 4 arguments - if you supply 3 arguments it is assumed you have
1088          * omitted the `children` parameter, and that the event handler should be bound directly to the given element.
1089          * @method on
1090          * @param {Element[]|Element|String} el Either an Element, or a CSS spec for a list of elements, or an array of Elements.
1091          * @param {String} [children] Comma-delimited list of selectors identifying allowed children.
1092          * @param {String} event Event ID.
1093          * @param {Function} fn Event handler function.
1094          * @return {Mottle} The current Mottle instance; you can chain this method.
1095          */
1096         this.on = function (el, event, children, fn) {
1097             var _el = arguments[0],
1098                 _c = arguments.length == 4 ? arguments[2] : null,
1099                 _e = arguments[1],
1100                 _f = arguments[arguments.length - 1];
1101 
1102             _doBind(_el, _e, _f, _c);
1103             return this;
1104         };
1105 
1106         /**
1107          * Cancel delegate event handling for the given function. Note that unlike with 'on' you do not supply
1108          * a list of child selectors here: it removes event delegation from all of the child selectors for which the
1109          * given function was registered (if any).
1110          * @method off
1111          * @param {Element[]|Element|String} el Element - or ID of element - from which to remove event listener.
1112          * @param {String} event Event ID.
1113          * @param {Function} fn Event handler function.
1114          * @return {Mottle} The current Mottle instance; you can chain this method.
1115          */
1116         this.off = function (el, event, fn) {
1117             _unbind(el, event, fn);
1118             return this;
1119         };
1120 
1121         /**
1122          * Triggers some event for a given element.
1123          * @method trigger
1124          * @param {Element} el Element for which to trigger the event.
1125          * @param {String} event Event ID.
1126          * @param {Event} originalEvent The original event. Should be optional of course, but currently is not, due
1127          * to the jsPlumb use case that caused this method to be added.
1128          * @param {Object} [payload] Optional object to set as `payload` on the generated event; useful for message passing.
1129          * @return {Mottle} The current Mottle instance; you can chain this method.
1130          */
1131         this.trigger = function (el, event, originalEvent, payload) {
1132             // MouseEvent undefined in old IE; that's how we know it's a mouse event.  A fine Microsoft paradox.
1133             var originalIsMouse = isMouseDevice && (typeof MouseEvent === "undefined" || originalEvent == null || originalEvent.constructor === MouseEvent);
1134 
1135             var eventToBind = (isTouchDevice && !isMouseDevice && touchMap[event]) ? touchMap[event] : event,
1136                 bindingAMouseEvent = !(isTouchDevice && !isMouseDevice && touchMap[event]);
1137 
1138             var pl = _pageLocation(originalEvent), sl = _screenLocation(originalEvent), cl = _clientLocation(originalEvent);
1139             _each(el, function () {
1140                 var _el = _gel(this), evt;
1141                 originalEvent = originalEvent || {
1142                     screenX: sl[0],
1143                     screenY: sl[1],
1144                     clientX: cl[0],
1145                     clientY: cl[1]
1146                 };
1147 
1148                 var _decorate = function (_evt) {
1149                     if (payload) _evt.payload = payload;
1150                 };
1151 
1152                 var eventGenerators = {
1153                     "TouchEvent": function (evt) {
1154                         var touch = document.createTouch(window, _el, 0, pl[0], pl[1],
1155                             sl[0], sl[1],
1156                             cl[0], cl[1],
1157                             0, 0, 0, 0);
1158 
1159                         // https://gist.github.com/sstephenson/448808
1160                         var touches = document.createTouchList(touch);
1161                         var targetTouches = document.createTouchList(touch);
1162                         var changedTouches = document.createTouchList(touch);
1163                         evt.initTouchEvent(eventToBind, true, true, window, null, sl[0], sl[1],
1164                             cl[0], cl[1], false, false, false, false,
1165                             touches, targetTouches, changedTouches, 1, 0);
1166                     },
1167                     "MouseEvents": function (evt) {
1168                         evt.initMouseEvent(eventToBind, true, true, window, 0,
1169                             sl[0], sl[1],
1170                             cl[0], cl[1],
1171                             false, false, false, false, 1, _el);
1172 
1173                         if (Sniff.android) {
1174                             // Android's touch events are not standard.
1175                             var t = document.createTouch(window, _el, 0, pl[0], pl[1],
1176                                 sl[0], sl[1],
1177                                 cl[0], cl[1],
1178                                 0, 0, 0, 0);
1179 
1180                             evt.touches = evt.targetTouches = evt.changedTouches = document.createTouchList(t);
1181                         }
1182                     }
1183                 };
1184 
1185                 if (document.createEvent) {
1186 
1187                     var ite = !bindingAMouseEvent && !originalIsMouse && (isTouchDevice && touchMap[event] && !Sniff.android),
1188                         evtName = ite ? "TouchEvent" : "MouseEvents";
1189 
1190                     evt = document.createEvent(evtName);
1191                     eventGenerators[evtName](evt);
1192                     _decorate(evt);
1193                     _el.dispatchEvent(evt);
1194                 }
1195                 else if (document.createEventObject) {
1196                     evt = document.createEventObject();
1197                     evt.eventType = evt.eventName = eventToBind;
1198                     evt.screenX = sl[0];
1199                     evt.screenY = sl[1];
1200                     evt.clientX = cl[0];
1201                     evt.clientY = cl[1];
1202                     _decorate(evt);
1203                     _el.fireEvent('on' + eventToBind, evt);
1204                 }
1205             });
1206             return this;
1207         }
1208     };
1209 
1210     /**
1211      * Static method to assist in 'consuming' an element: uses `stopPropagation` where available, or sets
1212      * `e.returnValue=false` where it is not.
1213      * @method Mottle.consume
1214      * @param {Event} e Event to consume
1215      * @param {Boolean} [doNotPreventDefault=false] If true, does not call `preventDefault()` on the event.
1216      */
1217     root.Mottle.consume = function (e, doNotPreventDefault) {
1218         if (e.stopPropagation)
1219             e.stopPropagation();
1220         else
1221             e.returnValue = false;
1222 
1223         if (!doNotPreventDefault && e.preventDefault)
1224             e.preventDefault();
1225     };
1226 
1227     /**
1228      * Gets the page location corresponding to the given event. For touch events this means get the page location of the first touch.
1229      * @method Mottle.pageLocation
1230      * @param {Event} e Event to get page location for.
1231      * @return {Number[]} [left, top] for the given event.
1232      */
1233     root.Mottle.pageLocation = _pageLocation;
1234 
1235     /**
1236      * Forces touch events to be turned "on". Useful for testing: even if you don't have a touch device, you can still
1237      * trigger a touch event when this is switched on and it will be captured and acted on.
1238      * @method setForceTouchEvents
1239      * @param {Boolean} value If true, force touch events to be on.
1240      */
1241     root.Mottle.setForceTouchEvents = function (value) {
1242         isTouchDevice = value;
1243     };
1244 
1245     /**
1246      * Forces mouse events to be turned "on". Useful for testing: even if you don't have a mouse, you can still
1247      * trigger a mouse event when this is switched on and it will be captured and acted on.
1248      * @method setForceMouseEvents
1249      * @param {Boolean} value If true, force mouse events to be on.
1250      */
1251     root.Mottle.setForceMouseEvents = function (value) {
1252         isMouseDevice = value;
1253     };
1254 
1255     root.Mottle.version = "0.8.0";
1256 
1257     if (typeof exports !== "undefined") {
1258         exports.Mottle = root.Mottle;
1259     }
1260 
1261 }).call(typeof window === "undefined" ? this : window);
1262 
1263 /**
1264  drag/drop functionality for use with jsPlumb but with
1265  no knowledge of jsPlumb. supports multiple scopes (separated by whitespace), dragging
1266  multiple elements, constrain to parent, drop filters, drag start filters, custom
1267  css classes.
1268 
1269  a lot of the functionality of this script is expected to be plugged in:
1270 
1271  addClass
1272  removeClass
1273 
1274  addEvent
1275  removeEvent
1276 
1277  getPosition
1278  setPosition
1279  getSize
1280 
1281  indexOf
1282  intersects
1283 
1284  the name came from here:
1285 
1286  http://mrsharpoblunto.github.io/foswig.js/
1287 
1288  copyright 2016 jsPlumb
1289  */
1290 
1291 ;(function() {
1292 
1293     "use strict";
1294     var root = this;
1295 
1296     var _suggest = function(list, item, head) {
1297         if (list.indexOf(item) === -1) {
1298             head ? list.unshift(item) : list.push(item);
1299             return true;
1300         }
1301         return false;
1302     };
1303 
1304     var _vanquish = function(list, item) {
1305         var idx = list.indexOf(item);
1306         if (idx != -1) list.splice(idx, 1);
1307     };
1308 
1309     var _difference = function(l1, l2) {
1310         var d = [];
1311         for (var i = 0; i < l1.length; i++) {
1312             if (l2.indexOf(l1[i]) == -1)
1313                 d.push(l1[i]);
1314         }
1315         return d;
1316     };
1317 
1318     var _isString = function(f) {
1319         return f == null ? false : (typeof f === "string" || f.constructor == String);
1320     };
1321 
1322     var getOffsetRect = function (elem) {
1323         // (1)
1324         var box = elem.getBoundingClientRect(),
1325             body = document.body,
1326             docElem = document.documentElement,
1327         // (2)
1328             scrollTop = window.pageYOffset || docElem.scrollTop || body.scrollTop,
1329             scrollLeft = window.pageXOffset || docElem.scrollLeft || body.scrollLeft,
1330         // (3)
1331             clientTop = docElem.clientTop || body.clientTop || 0,
1332             clientLeft = docElem.clientLeft || body.clientLeft || 0,
1333         // (4)
1334             top  = box.top +  scrollTop - clientTop,
1335             left = box.left + scrollLeft - clientLeft;
1336 
1337         return { top: Math.round(top), left: Math.round(left) };
1338     };
1339 
1340     var matchesSelector = function(el, selector, ctx) {
1341         ctx = ctx || el.parentNode;
1342         var possibles = ctx.querySelectorAll(selector);
1343         for (var i = 0; i < possibles.length; i++) {
1344             if (possibles[i] === el)
1345                 return true;
1346         }
1347         return false;
1348     };
1349 
1350     var iev = (function() {
1351             var rv = -1;
1352             if (navigator.appName == 'Microsoft Internet Explorer') {
1353                 var ua = navigator.userAgent,
1354                     re = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})");
1355                 if (re.exec(ua) != null)
1356                     rv = parseFloat(RegExp.$1);
1357             }
1358             return rv;
1359         })(),
1360         DEFAULT_GRID_X = 50,
1361         DEFAULT_GRID_Y = 50,
1362         isIELT9 = iev > -1 && iev < 9,
1363         isIE9 = iev == 9,
1364         _pl = function(e) {
1365             if (isIELT9) {
1366                 return [ e.clientX + document.documentElement.scrollLeft, e.clientY + document.documentElement.scrollTop ];
1367             }
1368             else {
1369                 var ts = _touches(e), t = _getTouch(ts, 0);
1370                 // for IE9 pageX might be null if the event was synthesized. We try for pageX/pageY first,
1371                 // falling back to clientX/clientY if necessary. In every other browser we want to use pageX/pageY.
1372                 return isIE9 ? [t.pageX || t.clientX, t.pageY || t.clientY] : [t.pageX, t.pageY];
1373             }
1374         },
1375         _getTouch = function(touches, idx) { return touches.item ? touches.item(idx) : touches[idx]; },
1376         _touches = function(e) {
1377             return e.touches && e.touches.length > 0 ? e.touches :
1378                     e.changedTouches && e.changedTouches.length > 0 ? e.changedTouches :
1379                     e.targetTouches && e.targetTouches.length > 0 ? e.targetTouches :
1380                 [ e ];
1381         },
1382         _classes = {
1383             draggable:"katavorio-draggable",    // draggable elements
1384             droppable:"katavorio-droppable",    // droppable elements
1385             drag : "katavorio-drag",            // elements currently being dragged
1386             selected:"katavorio-drag-selected", // elements in current drag selection
1387             active : "katavorio-drag-active",   // droppables that are targets of a currently dragged element
1388             hover : "katavorio-drag-hover",     // droppables over which a matching drag element is hovering
1389             noSelect : "katavorio-drag-no-select", // added to the body to provide a hook to suppress text selection
1390             ghostProxy:"katavorio-ghost-proxy"  // added to a ghost proxy element in use when a drag has exited the bounds of its parent.
1391         },
1392         _defaultScope = "katavorio-drag-scope",
1393         _events = [ "stop", "start", "drag", "drop", "over", "out", "beforeStart" ],
1394         _devNull = function() {},
1395         _true = function() { return true; },
1396         _foreach = function(l, fn, from) {
1397             for (var i = 0; i < l.length; i++) {
1398                 if (l[i] != from)
1399                     fn(l[i]);
1400             }
1401         },
1402         _setDroppablesActive = function(dd, val, andHover, drag) {
1403             _foreach(dd, function(e) {
1404                 e.setActive(val);
1405                 if (val) e.updatePosition();
1406                 if (andHover) e.setHover(drag, val);
1407             });
1408         },
1409         _each = function(obj, fn) {
1410             if (obj == null) return;
1411             obj = !_isString(obj) && (obj.tagName == null && obj.length != null) ? obj : [ obj ];
1412             for (var i = 0; i < obj.length; i++)
1413                 fn.apply(obj[i], [ obj[i] ]);
1414         },
1415         _consume = function(e) {
1416             if (e.stopPropagation) {
1417                 e.stopPropagation();
1418                 e.preventDefault();
1419             }
1420             else {
1421                 e.returnValue = false;
1422             }
1423         },
1424         _defaultInputFilterSelector = "input,textarea,select,button,option",
1425     //
1426     // filters out events on all input elements, like textarea, checkbox, input, select.
1427         _inputFilter = function(e, el, _katavorio) {
1428             var t = e.srcElement || e.target;
1429             return !matchesSelector(t, _katavorio.getInputFilterSelector(), el);
1430         };
1431 
1432     var Super = function(el, params, css, scope) {
1433         this.params = params || {};
1434         this.el = el;
1435         this.params.addClass(this.el, this._class);
1436         this.uuid = _uuid();
1437         var enabled = true;
1438         this.setEnabled = function(e) { enabled = e; };
1439         this.isEnabled = function() { return enabled; };
1440         this.toggleEnabled = function() { enabled = !enabled; };
1441         this.setScope = function(scopes) {
1442             this.scopes = scopes ? scopes.split(/\s+/) : [ scope ];
1443         };
1444         this.addScope = function(scopes) {
1445             var m = {};
1446             _each(this.scopes, function(s) { m[s] = true;});
1447             _each(scopes ? scopes.split(/\s+/) : [], function(s) { m[s] = true;});
1448             this.scopes = [];
1449             for (var i in m) this.scopes.push(i);
1450         };
1451         this.removeScope = function(scopes) {
1452             var m = {};
1453             _each(this.scopes, function(s) { m[s] = true;});
1454             _each(scopes ? scopes.split(/\s+/) : [], function(s) { delete m[s];});
1455             this.scopes = [];
1456             for (var i in m) this.scopes.push(i);
1457         };
1458         this.toggleScope = function(scopes) {
1459             var m = {};
1460             _each(this.scopes, function(s) { m[s] = true;});
1461             _each(scopes ? scopes.split(/\s+/) : [], function(s) {
1462                 if (m[s]) delete m[s];
1463                 else m[s] = true;
1464             });
1465             this.scopes = [];
1466             for (var i in m) this.scopes.push(i);
1467         };
1468         this.setScope(params.scope);
1469         this.k = params.katavorio;
1470         return params.katavorio;
1471     };
1472 
1473     var TRUE = function() { return true; };
1474     var FALSE = function() { return false; };
1475 
1476     var Drag = function(el, params, css, scope) {
1477         this._class = css.draggable;
1478         var k = Super.apply(this, arguments);
1479         this.rightButtonCanDrag = this.params.rightButtonCanDrag;
1480         var downAt = [0,0], posAtDown = null, pagePosAtDown = null, pageDelta = [0,0], moving = false,
1481             consumeStartEvent = this.params.consumeStartEvent !== false,
1482             dragEl = this.el,
1483             clone = this.params.clone,
1484             scroll = this.params.scroll,
1485             _multipleDrop = params.multipleDrop !== false,
1486             isConstrained = false,
1487             useGhostProxy = params.ghostProxy === true ? TRUE : params.ghostProxy && typeof params.ghostProxy === "function" ? params.ghostProxy : FALSE,
1488             ghostProxy = function(el) { return el.cloneNode(true); };
1489 
1490         var snapThreshold = params.snapThreshold || 5,
1491             _snap = function(pos, x, y, thresholdX, thresholdY) {
1492                 thresholdX = thresholdX || snapThreshold;
1493                 thresholdY = thresholdY || snapThreshold;
1494                 var _dx = Math.floor(pos[0] / x),
1495                     _dxl = x * _dx,
1496                     _dxt = _dxl + x,
1497                     _x = Math.abs(pos[0] - _dxl) <= thresholdX ? _dxl : Math.abs(_dxt - pos[0]) <= thresholdX ? _dxt : pos[0];
1498 
1499                 var _dy = Math.floor(pos[1] / y),
1500                     _dyl = y * _dy,
1501                     _dyt = _dyl + y,
1502                     _y = Math.abs(pos[1] - _dyl) <= thresholdY ? _dyl : Math.abs(_dyt - pos[1]) <= thresholdY ? _dyt : pos[1];
1503 
1504                 return [ _x, _y];
1505             };
1506 
1507         this.posses = [];
1508         this.posseRoles = {};
1509 
1510         this.toGrid = function(pos) {
1511             if (this.params.grid == null) {
1512                 return pos;
1513             }
1514             else {
1515                 return _snap(pos, this.params.grid[0], this.params.grid[1]);
1516             }
1517         };
1518 
1519         this.snap = function(x, y) {
1520             if (dragEl == null) return;
1521             x = x || (this.params.grid ? this.params.grid[0] : DEFAULT_GRID_X);
1522             y = y || (this.params.grid ? this.params.grid[1] : DEFAULT_GRID_Y);
1523             var p = this.params.getPosition(dragEl);
1524             this.params.setPosition(dragEl, _snap(p, x, y, x, y));
1525         };
1526 
1527         this.setUseGhostProxy = function(val) {
1528             useGhostProxy = val ? TRUE : FALSE;
1529         };
1530 
1531         var constrain;
1532         var negativeFilter = function(pos) {
1533             return (params.allowNegative === false) ? [ Math.max (0, pos[0]), Math.max(0, pos[1]) ] : pos;
1534         };
1535 
1536         var _setConstrain = function(value) {
1537             constrain = typeof value === "function" ? value : value ? function(pos) {
1538                 return negativeFilter([
1539                     Math.max(0, Math.min(constrainRect.w - this.size[0], pos[0])),
1540                     Math.max(0, Math.min(constrainRect.h - this.size[1], pos[1]))
1541                 ]);
1542             }.bind(this) : function(pos) { return negativeFilter(pos); };
1543         }.bind(this);
1544 
1545         _setConstrain(typeof this.params.constrain === "function" ? this.params.constrain  : (this.params.constrain || this.params.containment));
1546 
1547 
1548         /**
1549          * Sets whether or not the Drag is constrained. A value of 'true' means constrain to parent bounds; a function
1550          * will be executed and returns true if the position is allowed.
1551          * @param value
1552          */
1553         this.setConstrain = function(value) {
1554             _setConstrain(value);
1555         };
1556 
1557         var revertFunction;
1558         /**
1559          * Sets a function to call on drag stop, which, if it returns true, indicates that the given element should
1560          * revert to its position before the previous drag.
1561          * @param fn
1562          */
1563         this.setRevert = function(fn) {
1564             revertFunction = fn;
1565         };
1566 
1567         var _assignId = function(obj) {
1568                 if (typeof obj == "function") {
1569                     obj._katavorioId = _uuid();
1570                     return obj._katavorioId;
1571                 } else {
1572                     return obj;
1573                 }
1574             },
1575         // a map of { spec -> [ fn, exclusion ] } entries.
1576             _filters = {},
1577             _testFilter = function(e) {
1578                 for (var key in _filters) {
1579                     var f = _filters[key];
1580                     var rv = f[0](e);
1581                     if (f[1]) rv = !rv;
1582                     if (!rv) return false;
1583                 }
1584                 return true;
1585             },
1586             _setFilter = this.setFilter = function(f, _exclude) {
1587                 if (f) {
1588                     var key = _assignId(f);
1589                     _filters[key] = [
1590                         function(e) {
1591                             var t = e.srcElement || e.target, m;
1592                             if (_isString(f)) {
1593                                 m = matchesSelector(t, f, el);
1594                             }
1595                             else if (typeof f === "function") {
1596                                 m = f(e, el);
1597                             }
1598                             return m;
1599                         },
1600                             _exclude !== false
1601                     ];
1602 
1603                 }
1604             },
1605             _addFilter = this.addFilter = _setFilter,
1606             _removeFilter = this.removeFilter = function(f) {
1607                 var key = typeof f == "function" ? f._katavorioId : f;
1608                 delete _filters[key];
1609             };
1610 
1611         this.clearAllFilters = function() {
1612             _filters = {};
1613         };
1614 
1615         this.canDrag = this.params.canDrag || _true;
1616 
1617         var constrainRect,
1618             matchingDroppables = [], intersectingDroppables = [];
1619 
1620         this.downListener = function(e) {
1621             var isNotRightClick = this.rightButtonCanDrag || (e.which !== 3 && e.button !== 2);
1622             if (isNotRightClick && this.isEnabled() && this.canDrag()) {
1623                 var _f =  _testFilter(e) && _inputFilter(e, this.el, this.k);
1624                 if (_f) {
1625                     if (!clone)
1626                         dragEl = this.el;
1627                     else {
1628                         dragEl = this.el.cloneNode(true);
1629                         dragEl.setAttribute("id", null);
1630                         dragEl.style.position = "absolute";
1631                         // the clone node is added to the body; getOffsetRect gives us a value
1632                         // relative to the body.
1633                         var b = getOffsetRect(this.el);
1634                         dragEl.style.left = b.left + "px";
1635                         dragEl.style.top = b.top + "px";
1636                         document.body.appendChild(dragEl);
1637                     }
1638                     consumeStartEvent && _consume(e);
1639                     downAt = _pl(e);
1640                     //
1641                     this.params.bind(document, "mousemove", this.moveListener);
1642                     this.params.bind(document, "mouseup", this.upListener);
1643                     k.markSelection(this);
1644                     k.markPosses(this);
1645                     this.params.addClass(document.body, css.noSelect);
1646                     _dispatch("beforeStart", {el:this.el, pos:posAtDown, e:e, drag:this});
1647                 }
1648                 else if (this.params.consumeFilteredEvents) {
1649                     _consume(e);
1650                 }
1651             }
1652         }.bind(this);
1653 
1654         this.moveListener = function(e) {
1655             if (downAt) {
1656                 if (!moving) {
1657                     var _continue = _dispatch("start", {el:this.el, pos:posAtDown, e:e, drag:this});
1658                     if (_continue !== false) {
1659                         if (!downAt) return;
1660                         this.mark(true);
1661                         moving = true;
1662                     }
1663                 }
1664 
1665                 // it is possible that the start event caused the drag to be aborted. So we check
1666                 // again that we are currently dragging.
1667                 if (downAt) {
1668                     intersectingDroppables.length = 0;
1669                     var pos = _pl(e), dx = pos[0] - downAt[0], dy = pos[1] - downAt[1],
1670                         z = this.params.ignoreZoom ? 1 : k.getZoom();
1671                     dx /= z;
1672                     dy /= z;
1673                     this.moveBy(dx, dy, e);
1674                     k.updateSelection(dx, dy, this);
1675                     k.updatePosses(dx, dy, this);
1676                 }
1677             }
1678         }.bind(this);
1679 
1680         this.upListener = function(e) {
1681             if (downAt) {
1682                 downAt = null;
1683                 this.params.unbind(document, "mousemove", this.moveListener);
1684                 this.params.unbind(document, "mouseup", this.upListener);
1685                 this.params.removeClass(document.body, css.noSelect);
1686                 this.unmark(e);
1687                 k.unmarkSelection(this, e);
1688                 k.unmarkPosses(this, e);
1689                 this.stop(e);
1690                 k.notifySelectionDragStop(this, e);
1691                 k.notifyPosseDragStop(this, e);
1692                 moving = false;
1693                 if (clone) {
1694                     dragEl && dragEl.parentNode && dragEl.parentNode.removeChild(dragEl);
1695                     dragEl = null;
1696                 }
1697 
1698                 intersectingDroppables.length = 0;
1699 
1700                 if (revertFunction && revertFunction(this.el, this.params.getPosition(this.el)) === true) {
1701                     this.params.setPosition(this.el, posAtDown);
1702                     _dispatch("revert", this.el);
1703                 }
1704             }
1705         }.bind(this);
1706 
1707         this.getFilters = function() { return _filters; };
1708 
1709         this.abort = function() {
1710             if (downAt != null)
1711                 this.upListener();
1712         };
1713 
1714         this.getDragElement = function() {
1715             return dragEl || this.el;
1716         };
1717 
1718         var listeners = {"start":[], "drag":[], "stop":[], "over":[], "out":[], "beforeStart":[], "revert":[] };
1719         if (params.events.start) listeners.start.push(params.events.start);
1720         if (params.events.beforeStart) listeners.beforeStart.push(params.events.beforeStart);
1721         if (params.events.stop) listeners.stop.push(params.events.stop);
1722         if (params.events.drag) listeners.drag.push(params.events.drag);
1723         if (params.events.revert) listeners.revert.push(params.events.revert);
1724 
1725         this.on = function(evt, fn) {
1726             if (listeners[evt]) listeners[evt].push(fn);
1727         };
1728 
1729         this.off = function(evt, fn) {
1730             if (listeners[evt]) {
1731                 var l = [];
1732                 for (var i = 0; i < listeners[evt].length; i++) {
1733                     if (listeners[evt][i] !== fn) l.push(listeners[evt][i]);
1734                 }
1735                 listeners[evt] = l;
1736             }
1737         };
1738 
1739         var _dispatch = function(evt, value) {
1740             if (listeners[evt]) {
1741                 for (var i = 0; i < listeners[evt].length; i++) {
1742                     try {
1743                         listeners[evt][i](value);
1744                     }
1745                     catch (e) { }
1746                 }
1747             }
1748         };
1749 
1750         this.notifyStart = function(e) {
1751             _dispatch("start", {el:this.el, pos:this.params.getPosition(dragEl), e:e, drag:this});
1752         };
1753 
1754         this.stop = function(e, force) {
1755             if (force || moving) {
1756                 var positions = [],
1757                     sel = k.getSelection(),
1758                     dPos = this.params.getPosition(dragEl);
1759 
1760                 if (sel.length > 1) {
1761                     for (var i = 0; i < sel.length; i++) {
1762                         var p = this.params.getPosition(sel[i].el);
1763                         positions.push([ sel[i].el, { left: p[0], top: p[1] }, sel[i] ]);
1764                     }
1765                 }
1766                 else {
1767                     positions.push([ dragEl, {left:dPos[0], top:dPos[1]}, this ]);
1768                 }
1769 
1770                 _dispatch("stop", {
1771                     el: dragEl,
1772                     pos: ghostProxyOffsets || dPos,
1773                     finalPos:dPos,
1774                     e: e,
1775                     drag: this,
1776                     selection:positions
1777                 });
1778             }
1779         };
1780 
1781         this.mark = function(andNotify) {
1782             posAtDown = this.params.getPosition(dragEl);
1783             pagePosAtDown = this.params.getPosition(dragEl, true);
1784             pageDelta = [pagePosAtDown[0] - posAtDown[0], pagePosAtDown[1] - posAtDown[1]];
1785             this.size = this.params.getSize(dragEl);
1786             matchingDroppables = k.getMatchingDroppables(this);
1787             _setDroppablesActive(matchingDroppables, true, false, this);
1788             this.params.addClass(dragEl, this.params.dragClass || css.drag);
1789             //if (this.params.constrain || this.params.containment) {
1790             var cs = this.params.getSize(dragEl.parentNode);
1791             constrainRect = { w:cs[0], h:cs[1] };
1792             //}
1793             if (andNotify) {
1794                 k.notifySelectionDragStart(this);
1795             }
1796         };
1797         var ghostProxyOffsets;
1798         this.unmark = function(e, doNotCheckDroppables) {
1799             _setDroppablesActive(matchingDroppables, false, true, this);
1800 
1801 
1802             if (isConstrained && useGhostProxy(this.el)) {
1803                 ghostProxyOffsets = [dragEl.offsetLeft, dragEl.offsetTop];
1804                 this.el.parentNode.removeChild(dragEl);
1805                 dragEl = this.el;
1806             }
1807             else {
1808                 ghostProxyOffsets = null;
1809             }
1810 
1811             this.params.removeClass(dragEl, this.params.dragClass || css.drag);
1812             matchingDroppables.length = 0;
1813             isConstrained = false;
1814             if (!doNotCheckDroppables) {
1815                 if (intersectingDroppables.length > 0 && ghostProxyOffsets) {
1816                     params.setPosition(this.el, ghostProxyOffsets);
1817                 }
1818                 intersectingDroppables.sort(_rankSort);
1819                 for (var i = 0; i < intersectingDroppables.length; i++) {
1820                     var retVal = intersectingDroppables[i].drop(this, e);
1821                     if (retVal === true) break;
1822                 }
1823             }
1824         };
1825         this.moveBy = function(dx, dy, e) {
1826             intersectingDroppables.length = 0;
1827             var desiredLoc = this.toGrid([posAtDown[0] + dx, posAtDown[1] + dy]),
1828                 cPos = constrain(desiredLoc, dragEl);
1829 
1830             if (useGhostProxy(this.el)) {
1831                 if (desiredLoc[0] != cPos[0] || desiredLoc[1] != cPos[1]) {
1832                     if (!isConstrained) {
1833                         var gp = ghostProxy(this.el);
1834                         params.addClass(gp, _classes.ghostProxy);
1835                         this.el.parentNode.appendChild(gp);
1836                         dragEl = gp;
1837                         isConstrained = true;
1838                     }
1839                     cPos = desiredLoc;
1840                 }
1841                 else {
1842                     if (isConstrained) {
1843                         this.el.parentNode.removeChild(dragEl);
1844                         dragEl = this.el;
1845                         isConstrained = false;
1846                     }
1847                 }
1848             }
1849 
1850             var rect = { x:cPos[0], y:cPos[1], w:this.size[0], h:this.size[1]},
1851                 pageRect = { x:rect.x + pageDelta[0], y:rect.y + pageDelta[1], w:rect.w, h:rect.h},
1852                 focusDropElement = null;
1853 
1854 
1855 
1856             this.params.setPosition(dragEl, cPos);
1857             for (var i = 0; i < matchingDroppables.length; i++) {
1858                 var r2 = { x:matchingDroppables[i].pagePosition[0], y:matchingDroppables[i].pagePosition[1], w:matchingDroppables[i].size[0], h:matchingDroppables[i].size[1]};
1859                 if (this.params.intersects(pageRect, r2) && (_multipleDrop || focusDropElement == null || focusDropElement == matchingDroppables[i].el) && matchingDroppables[i].canDrop(this)) {
1860                     if (!focusDropElement) focusDropElement = matchingDroppables[i].el;
1861                     intersectingDroppables.push(matchingDroppables[i]);
1862                     matchingDroppables[i].setHover(this, true, e);
1863                 }
1864                 else if (matchingDroppables[i].isHover()) {
1865                     matchingDroppables[i].setHover(this, false, e);
1866                 }
1867             }
1868 
1869             _dispatch("drag", {el:this.el, pos:cPos, e:e, drag:this});
1870 
1871             /* test to see if the parent needs to be scrolled (future)
1872              if (scroll) {
1873              var pnsl = dragEl.parentNode.scrollLeft, pnst = dragEl.parentNode.scrollTop;
1874              console.log("scroll!", pnsl, pnst);
1875              }*/
1876         };
1877         this.destroy = function() {
1878             this.params.unbind(this.el, "mousedown", this.downListener);
1879             this.params.unbind(document, "mousemove", this.moveListener);
1880             this.params.unbind(document, "mouseup", this.upListener);
1881             this.downListener = null;
1882             this.upListener = null;
1883             this.moveListener = null;
1884         };
1885 
1886         // init:register mousedown, and perhaps set a filter
1887         this.params.bind(this.el, "mousedown", this.downListener);
1888 
1889         // if handle provded, use that.  otherwise, try to set a filter.
1890         // note that a `handle` selector always results in filterExclude being set to false, ie.
1891         // the selector defines the handle element(s).
1892         if (this.params.handle)
1893             _setFilter(this.params.handle, false);
1894         else
1895             _setFilter(this.params.filter, this.params.filterExclude);
1896     };
1897 
1898     var Drop = function(el, params, css, scope) {
1899         this._class = css.droppable;
1900         this.params = params || {};
1901         this.rank = params.rank || 0;
1902         this._activeClass = this.params.activeClass || css.active;
1903         this._hoverClass = this.params.hoverClass || css.hover;
1904         Super.apply(this, arguments);
1905         var hover = false;
1906         this.allowLoopback = this.params.allowLoopback !== false;
1907 
1908         this.setActive = function(val) {
1909             this.params[val ? "addClass" : "removeClass"](this.el, this._activeClass);
1910         };
1911 
1912         this.updatePosition = function() {
1913             this.position = this.params.getPosition(this.el);
1914             this.pagePosition = this.params.getPosition(this.el, true);
1915             this.size = this.params.getSize(this.el);
1916         };
1917 
1918         this.canDrop = this.params.canDrop || function(drag) {
1919             return true;
1920         };
1921 
1922         this.isHover = function() { return hover; };
1923 
1924         this.setHover = function(drag, val, e) {
1925             // if turning off hover but this was not the drag that caused the hover, ignore.
1926             if (val || this.el._katavorioDragHover == null || this.el._katavorioDragHover == drag.el._katavorio) {
1927                 this.params[val ? "addClass" : "removeClass"](this.el, this._hoverClass);
1928                 //this.el._katavorioDragHover = val ? drag.el._katavorio : null;
1929                 this.el._katavorioDragHover = val ? drag.el._katavorio : null;
1930                 if (hover !== val)
1931                     this.params.events[val ? "over" : "out"]({el:this.el, e:e, drag:drag, drop:this});
1932                 hover = val;
1933             }
1934         };
1935 
1936         this.drop = function(drag, event) {
1937             return this.params.events["drop"]({ drag:drag, e:event, drop:this });
1938         };
1939 
1940         this.destroy = function() {
1941             this._class = null;
1942             this._activeClass = null;
1943             this._hoverClass = null;
1944             //this.params = null;
1945             hover = null;
1946             //this.el = null;
1947         };
1948     };
1949 
1950     var _uuid = function() {
1951         return ('xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
1952             var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
1953             return v.toString(16);
1954         }));
1955     };
1956 
1957     var _rankSort = function(a,b) {
1958         return a.rank < b.rank ? 1 : a.rank > b.rank ? -1 : 0;
1959     };
1960 
1961     var _gel = function(el) {
1962         if (el == null) return null;
1963         el = (typeof el === "string" || el.constructor == String)  ? document.getElementById(el) : el;
1964         if (el == null) return null;
1965         el._katavorio = el._katavorio || _uuid();
1966         return el;
1967     };
1968 
1969     root.Katavorio = function(katavorioParams) {
1970 
1971         var _selection = [],
1972             _selectionMap = {};
1973 
1974         this._dragsByScope = {};
1975         this._dropsByScope = {};
1976         var _zoom = 1,
1977             _reg = function(obj, map) {
1978                 _each(obj, function(_obj) {
1979                     for(var i = 0; i < _obj.scopes.length; i++) {
1980                         map[_obj.scopes[i]] = map[_obj.scopes[i]] || [];
1981                         map[_obj.scopes[i]].push(_obj);
1982                     }
1983                 });
1984             },
1985             _unreg = function(obj, map) {
1986                 var c = 0;
1987                 _each(obj, function(_obj) {
1988                     for(var i = 0; i < _obj.scopes.length; i++) {
1989                         if (map[_obj.scopes[i]]) {
1990                             var idx = katavorioParams.indexOf(map[_obj.scopes[i]], _obj);
1991                             if (idx != -1) {
1992                                 map[_obj.scopes[i]].splice(idx, 1);
1993                                 c++;
1994                             }
1995                         }
1996                     }
1997                 });
1998 
1999                 return c > 0 ;
2000             },
2001             _getMatchingDroppables = this.getMatchingDroppables = function(drag) {
2002                 var dd = [], _m = {};
2003                 for (var i = 0; i < drag.scopes.length; i++) {
2004                     var _dd = this._dropsByScope[drag.scopes[i]];
2005                     if (_dd) {
2006                         for (var j = 0; j < _dd.length; j++) {
2007                             if (_dd[j].canDrop(drag) &&  !_m[_dd[j].uuid] && (_dd[j].allowLoopback || _dd[j].el !== drag.el)) {
2008                                 _m[_dd[j].uuid] = true;
2009                                 dd.push(_dd[j]);
2010                             }
2011                         }
2012                     }
2013                 }
2014                 dd.sort(_rankSort);
2015                 return dd;
2016             },
2017             _prepareParams = function(p) {
2018                 p = p || {};
2019                 var _p = {
2020                     events:{}
2021                 }, i;
2022                 for (i in katavorioParams) _p[i] = katavorioParams[i];
2023                 for (i in p) _p[i] = p[i];
2024                 // events
2025 
2026                 for (i = 0; i < _events.length; i++) {
2027                     _p.events[_events[i]] = p[_events[i]] || _devNull;
2028                 }
2029                 _p.katavorio = this;
2030                 return _p;
2031             }.bind(this),
2032             _mistletoe = function(existingDrag, params) {
2033                 for (var i = 0; i < _events.length; i++) {
2034                     if (params[_events[i]]) {
2035                         existingDrag.on(_events[i], params[_events[i]]);
2036                     }
2037                 }
2038             }.bind(this),
2039             _css = {},
2040             overrideCss = katavorioParams.css || {},
2041             _scope = katavorioParams.scope || _defaultScope;
2042 
2043         // prepare map of css classes based on defaults frst, then optional overrides
2044         for (var i in _classes) _css[i] = _classes[i];
2045         for (var i in overrideCss) _css[i] = overrideCss[i];
2046 
2047         var inputFilterSelector = katavorioParams.inputFilterSelector || _defaultInputFilterSelector;
2048         /**
2049          * Gets the selector identifying which input elements to filter from drag events.
2050          * @method getInputFilterSelector
2051          * @return {String} Current input filter selector.
2052          */
2053         this.getInputFilterSelector = function() { return inputFilterSelector; };
2054 
2055         /**
2056          * Sets the selector identifying which input elements to filter from drag events.
2057          * @method setInputFilterSelector
2058          * @param {String} selector Input filter selector to set.
2059          * @return {Katavorio} Current instance; method may be chained.
2060          */
2061         this.setInputFilterSelector = function(selector) {
2062             inputFilterSelector = selector;
2063             return this;
2064         };
2065 
2066         this.draggable = function(el, params) {
2067             var o = [];
2068             _each(el, function(_el) {
2069                 _el = _gel(_el);
2070                 if (_el != null) {
2071                     if (_el._katavorioDrag == null) {
2072                         var p = _prepareParams(params);
2073                         _el._katavorioDrag = new Drag(_el, p, _css, _scope);
2074                         _reg(_el._katavorioDrag, this._dragsByScope);
2075                         o.push(_el._katavorioDrag);
2076                         katavorioParams.addClass(_el, _css.draggable);
2077                     }
2078                     else {
2079                         _mistletoe(_el._katavorioDrag, params);
2080                     }
2081                 }
2082             }.bind(this));
2083             return o;
2084 
2085         };
2086 
2087         this.droppable = function(el, params) {
2088             var o = [];
2089             _each(el, function(_el) {
2090                 _el = _gel(_el);
2091                 if (_el != null) {
2092                     var drop = new Drop(_el, _prepareParams(params), _css, _scope);
2093                     _el._katavorioDrop = _el._katavorioDrop || [];
2094                     _el._katavorioDrop.push(drop);
2095                     _reg(drop, this._dropsByScope);
2096                     o.push(drop);
2097                     katavorioParams.addClass(_el, _css.droppable);
2098                 }
2099             }.bind(this));
2100             return o;
2101         };
2102 
2103         /**
2104          * @name Katavorio#select
2105          * @function
2106          * @desc Adds an element to the current selection (for multiple node drag)
2107          * @param {Element|String} DOM element - or id of the element - to add.
2108          */
2109         this.select = function(el) {
2110             _each(el, function() {
2111                 var _el = _gel(this);
2112                 if (_el && _el._katavorioDrag) {
2113                     if (!_selectionMap[_el._katavorio]) {
2114                         _selection.push(_el._katavorioDrag);
2115                         _selectionMap[_el._katavorio] = [ _el, _selection.length - 1 ];
2116                         katavorioParams.addClass(_el, _css.selected);
2117                     }
2118                 }
2119             });
2120             return this;
2121         };
2122 
2123         /**
2124          * @name Katavorio#deselect
2125          * @function
2126          * @desc Removes an element from the current selection (for multiple node drag)
2127          * @param {Element|String} DOM element - or id of the element - to remove.
2128          */
2129         this.deselect = function(el) {
2130             _each(el, function() {
2131                 var _el = _gel(this);
2132                 if (_el && _el._katavorio) {
2133                     var e = _selectionMap[_el._katavorio];
2134                     if (e) {
2135                         var _s = [];
2136                         for (var i = 0; i < _selection.length; i++)
2137                             if (_selection[i].el !== _el) _s.push(_selection[i]);
2138                         _selection = _s;
2139                         delete _selectionMap[_el._katavorio];
2140                         katavorioParams.removeClass(_el, _css.selected);
2141                     }
2142                 }
2143             });
2144             return this;
2145         };
2146 
2147         this.deselectAll = function() {
2148             for (var i in _selectionMap) {
2149                 var d = _selectionMap[i];
2150                 katavorioParams.removeClass(d[0], _css.selected);
2151             }
2152 
2153             _selection.length = 0;
2154             _selectionMap = {};
2155         };
2156 
2157         this.markSelection = function(drag) {
2158             _foreach(_selection, function(e) { e.mark(); }, drag);
2159         };
2160 
2161         this.markPosses = function(drag) {
2162             if (drag.posses) {
2163                 _each(drag.posses, function(p) {
2164                     if (drag.posseRoles[p] && _posses[p]) {
2165                         _foreach(_posses[p].members, function (d) {
2166                             d.mark();
2167                         }, drag);
2168                     }
2169                 })
2170             }
2171         };
2172 
2173         this.unmarkSelection = function(drag, event) {
2174             _foreach(_selection, function(e) { e.unmark(event); }, drag);
2175         };
2176 
2177         this.unmarkPosses = function(drag, event) {
2178             if (drag.posses) {
2179                 _each(drag.posses, function(p) {
2180                     if (drag.posseRoles[p] && _posses[p]) {
2181                         _foreach(_posses[p].members, function (d) {
2182                             d.unmark(event, true);
2183                         }, drag);
2184                     }
2185                 });
2186             }
2187         };
2188 
2189         this.getSelection = function() { return _selection.slice(0); };
2190 
2191         this.updateSelection = function(dx, dy, drag) {
2192             _foreach(_selection, function(e) { e.moveBy(dx, dy); }, drag);
2193         };
2194 
2195         var _posseAction = function(fn, drag) {
2196             if (drag.posses) {
2197                 _each(drag.posses, function(p) {
2198                     if (drag.posseRoles[p] && _posses[p]) {
2199                         _foreach(_posses[p].members, function (e) {
2200                             fn(e);
2201                         }, drag);
2202                     }
2203                 });
2204             }
2205         };
2206 
2207         this.updatePosses = function(dx, dy, drag) {
2208             _posseAction(function(e) { e.moveBy(dx, dy); }, drag);
2209         };
2210 
2211         this.notifyPosseDragStop = function(drag, evt) {
2212             _posseAction(function(e) { e.stop(evt, true); }, drag);
2213         };
2214 
2215         this.notifySelectionDragStop = function(drag, evt) {
2216             _foreach(_selection, function(e) { e.stop(evt, true); }, drag);
2217         };
2218 
2219         this.notifySelectionDragStart = function(drag, evt) {
2220             _foreach(_selection, function(e) { e.notifyStart(evt);}, drag);
2221         };
2222 
2223         this.setZoom = function(z) { _zoom = z; };
2224         this.getZoom = function() { return _zoom; };
2225 
2226         // does the work of changing scopes
2227         var _scopeManip = function(kObj, scopes, map, fn) {
2228             _each(kObj, function(_kObj) {
2229                 _unreg(_kObj, map);  // deregister existing scopes
2230                 _kObj[fn](scopes); // set scopes
2231                 _reg(_kObj, map); // register new ones
2232             });
2233         };
2234 
2235         _each([ "set", "add", "remove", "toggle"], function(v) {
2236             this[v + "Scope"] = function(el, scopes) {
2237                 _scopeManip(el._katavorioDrag, scopes, this._dragsByScope, v + "Scope");
2238                 _scopeManip(el._katavorioDrop, scopes, this._dropsByScope, v + "Scope");
2239             }.bind(this);
2240             this[v + "DragScope"] = function(el, scopes) {
2241                 _scopeManip(el.constructor === Drag ? el : el._katavorioDrag, scopes, this._dragsByScope, v + "Scope");
2242             }.bind(this);
2243             this[v + "DropScope"] = function(el, scopes) {
2244                 _scopeManip(el.constructor === Drop ? el : el._katavorioDrop, scopes, this._dropsByScope, v + "Scope");
2245             }.bind(this);
2246         }.bind(this));
2247 
2248         this.snapToGrid = function(x, y) {
2249             for (var s in this._dragsByScope) {
2250                 _foreach(this._dragsByScope[s], function(d) { d.snap(x, y); });
2251             }
2252         };
2253 
2254         this.getDragsForScope = function(s) { return this._dragsByScope[s]; };
2255         this.getDropsForScope = function(s) { return this._dropsByScope[s]; };
2256 
2257         var _destroy = function(el, type, map) {
2258             el = _gel(el);
2259             if (el[type]) {
2260 
2261                 // remove from selection, if present.
2262                 var selIdx = _selection.indexOf(el[type]);
2263                 if (selIdx >= 0) {
2264                     _selection.splice(selIdx, 1);
2265                 }
2266 
2267                 if (_unreg(el[type], map)) {
2268                     _each(el[type], function(kObj) { kObj.destroy() });
2269                 }
2270 
2271                 delete el[type];
2272             }
2273         };
2274 
2275         this.elementRemoved = function(el) {
2276             this.destroyDraggable(el);
2277             this.destroyDroppable(el);
2278         };
2279 
2280         this.destroyDraggable = function(el) {
2281             _destroy(el, "_katavorioDrag", this._dragsByScope);
2282         };
2283 
2284         this.destroyDroppable = function(el) {
2285             _destroy(el, "_katavorioDrop", this._dropsByScope);
2286         };
2287 
2288         this.reset = function() {
2289             this._dragsByScope = {};
2290             this._dropsByScope = {};
2291             _selection = [];
2292             _selectionMap = {};
2293             _posses = {};
2294         };
2295 
2296         // ----- groups
2297         var _posses = {};
2298 
2299         var _processOneSpec = function(el, _spec, dontAddExisting) {
2300             var posseId = _isString(_spec) ? _spec : _spec.id;
2301             var active = _isString(_spec) ? true : _spec.active !== false;
2302             var posse = _posses[posseId] || (function() {
2303                 var g = {name:posseId, members:[]};
2304                 _posses[posseId] = g;
2305                 return g;
2306             })();
2307             _each(el, function(_el) {
2308                 if (_el._katavorioDrag) {
2309 
2310                     if (dontAddExisting && _el._katavorioDrag.posseRoles[posse.name] != null) return;
2311 
2312                     _suggest(posse.members, _el._katavorioDrag);
2313                     _suggest(_el._katavorioDrag.posses, posse.name);
2314                     _el._katavorioDrag.posseRoles[posse.name] = active;
2315                 }
2316             });
2317             return posse;
2318         };
2319 
2320         /**
2321          * Add the given element to the posse with the given id, creating the group if it at first does not exist.
2322          * @method addToPosse
2323          * @param {Element} el Element to add.
2324          * @param {String...|Object...} spec Variable args parameters. Each argument can be a either a String, indicating
2325          * the ID of a Posse to which the element should be added as an active participant, or an Object containing
2326          * `{ id:"posseId", active:false/true}`. In the latter case, if `active` is not provided it is assumed to be
2327          * true.
2328          * @returns {Posse|Posse[]} The Posse(s) to which the element(s) was/were added.
2329          */
2330         this.addToPosse = function(el, spec) {
2331 
2332             var posses = [];
2333 
2334             for (var i = 1; i < arguments.length; i++) {
2335                 posses.push(_processOneSpec(el, arguments[i]));
2336             }
2337 
2338             return posses.length == 1 ? posses[0] : posses;
2339         };
2340 
2341         /**
2342          * Sets the posse(s) for the element with the given id, creating those that do not yet exist, and removing from
2343          * the element any current Posses that are not specified by this method call. This method will not change the
2344          * active/passive state if it is given a posse in which the element is already a member.
2345          * @method setPosse
2346          * @param {Element} el Element to set posse(s) on.
2347          * @param {String...|Object...} spec Variable args parameters. Each argument can be a either a String, indicating
2348          * the ID of a Posse to which the element should be added as an active participant, or an Object containing
2349          * `{ id:"posseId", active:false/true}`. In the latter case, if `active` is not provided it is assumed to be
2350          * true.
2351          * @returns {Posse|Posse[]} The Posse(s) to which the element(s) now belongs.
2352          */
2353         this.setPosse = function(el, spec) {
2354 
2355             var posses = [];
2356 
2357             for (var i = 1; i < arguments.length; i++) {
2358                 posses.push(_processOneSpec(el, arguments[i], true).name);
2359             }
2360 
2361             _each(el, function(_el) {
2362                 if (_el._katavorioDrag) {
2363                     var diff = _difference(_el._katavorioDrag.posses, posses);
2364                     var p = [];
2365                     Array.prototype.push.apply(p, _el._katavorioDrag.posses);
2366                     for (var i = 0; i < diff.length; i++) {
2367                         this.removeFromPosse(_el, diff[i]);
2368                     }
2369                 }
2370             }.bind(this));
2371 
2372             return posses.length == 1 ? posses[0] : posses;
2373         };
2374 
2375         /**
2376          * Remove the given element from the given posse(s).
2377          * @method removeFromPosse
2378          * @param {Element} el Element to remove.
2379          * @param {String...} posseId Varargs parameter: one value for each posse to remove the element from.
2380          */
2381         this.removeFromPosse = function(el, posseId) {
2382             if (arguments.length < 2) throw new TypeError("No posse id provided for remove operation");
2383             for(var i = 1; i < arguments.length; i++) {
2384                 posseId = arguments[i];
2385                 _each(el, function (_el) {
2386                     if (_el._katavorioDrag && _el._katavorioDrag.posses) {
2387                         var d = _el._katavorioDrag;
2388                         _each(posseId, function (p) {
2389                             _vanquish(_posses[p].members, d);
2390                             _vanquish(d.posses, p);
2391                             delete d.posseRoles[p];
2392                         });
2393                     }
2394                 });
2395             }
2396         };
2397 
2398         /**
2399          * Remove the given element from all Posses to which it belongs.
2400          * @method removeFromAllPosses
2401          * @param {Element|Element[]} el Element to remove from Posses.
2402          */
2403         this.removeFromAllPosses = function(el) {
2404             _each(el, function(_el) {
2405                 if (_el._katavorioDrag && _el._katavorioDrag.posses) {
2406                     var d = _el._katavorioDrag;
2407                     _each(d.posses, function(p) {
2408                         _vanquish(_posses[p].members, d);
2409                     });
2410                     d.posses.length = 0;
2411                     d.posseRoles = {};
2412                 }
2413             });
2414         };
2415 
2416         /**
2417          * Changes the participation state for the element in the Posse with the given ID.
2418          * @param {Element|Element[]} el Element(s) to change state for.
2419          * @param {String} posseId ID of the Posse to change element state for.
2420          * @param {Boolean} state True to make active, false to make passive.
2421          */
2422         this.setPosseState = function(el, posseId, state) {
2423             var posse = _posses[posseId];
2424             if (posse) {
2425                 _each(el, function(_el) {
2426                     if (_el._katavorioDrag && _el._katavorioDrag.posses) {
2427                         _el._katavorioDrag.posseRoles[posse.name] = state;
2428                     }
2429                 });
2430             }
2431         };
2432 
2433     };
2434 
2435     root.Katavorio.version = "0.19.2";
2436 
2437     if (typeof exports !== "undefined") {
2438         exports.Katavorio = root.Katavorio;
2439     }
2440 
2441 }).call(typeof window !== 'undefined' ? window : this);
2442 
2443 /*
2444  * jsPlumb
2445  *
2446  * Title:jsPlumb 2.3.0
2447  *
2448  * Provides a way to visually connect elements on an HTML page, using SVG.
2449  *
2450  * This file contains utility functions that run in both browsers and headless.
2451  *
2452  * Copyright (c) 2010 - 2017 jsPlumb (hello@jsplumbtoolkit.com)
2453  *
2454  * http://jsplumbtoolkit.com
2455  * http://github.com/sporritt/jsplumb
2456  *
2457  * Dual licensed under the MIT and GPL2 licenses.
2458  */
2459 
2460 ;
2461 (function () {
2462 
2463     var _isa = function (a) {
2464             return Object.prototype.toString.call(a) === "[object Array]";
2465         },
2466         _isnum = function (n) {
2467             return Object.prototype.toString.call(n) === "[object Number]";
2468         },
2469         _iss = function (s) {
2470             return typeof s === "string";
2471         },
2472         _isb = function (s) {
2473             return typeof s === "boolean";
2474         },
2475         _isnull = function (s) {
2476             return s == null;
2477         },
2478         _iso = function (o) {
2479             return o == null ? false : Object.prototype.toString.call(o) === "[object Object]";
2480         },
2481         _isd = function (o) {
2482             return Object.prototype.toString.call(o) === "[object Date]";
2483         },
2484         _isf = function (o) {
2485             return Object.prototype.toString.call(o) === "[object Function]";
2486         },
2487         _isNamedFunction = function(o) {
2488             return _isf(o) && o.name != null && o.name.length > 0;
2489         },
2490         _ise = function (o) {
2491             for (var i in o) {
2492                 if (o.hasOwnProperty(i)) {
2493                     return false;
2494                 }
2495             }
2496             return true;
2497         };
2498 
2499     var root = this;
2500     root.jsPlumbUtil = {
2501         isArray: _isa,
2502         isString: _iss,
2503         isBoolean: _isb,
2504         isNull: _isnull,
2505         isObject: _iso,
2506         isDate: _isd,
2507         isFunction: _isf,
2508         isEmpty: _ise,
2509         isNumber: _isnum,
2510         clone: function (a) {
2511             if (_iss(a)) {
2512                 return "" + a;
2513             }
2514             else if (_isb(a)) {
2515                 return !!a;
2516             }
2517             else if (_isd(a)) {
2518                 return new Date(a.getTime());
2519             }
2520             else if (_isf(a)) {
2521                 return a;
2522             }
2523             else if (_isa(a)) {
2524                 var b = [];
2525                 for (var i = 0; i < a.length; i++) {
2526                     b.push(this.clone(a[i]));
2527                 }
2528                 return b;
2529             }
2530             else if (_iso(a)) {
2531                 var c = {};
2532                 for (var j in a) {
2533                     c[j] = this.clone(a[j]);
2534                 }
2535                 return c;
2536             }
2537             else {
2538                 return a;
2539             }
2540         },
2541         merge: function (a, b, collations) {
2542             // first change the collations array - if present - into a lookup table, because its faster.
2543             var cMap = {}, ar, i;
2544             collations = collations || [];
2545             for (i = 0; i < collations.length; i++) {
2546                 cMap[collations[i]] = true;
2547             }
2548 
2549             var c = this.clone(a);
2550             for (i in b) {
2551                 if (c[i] == null) {
2552                     c[i] = b[i];
2553                 }
2554                 else if (_iss(b[i]) || _isb(b[i])) {
2555                     if (!cMap[i]) {
2556                         c[i] = b[i]; // if we dont want to collate, just copy it in.
2557                     }
2558                     else {
2559                         ar = [];
2560                         // if c's object is also an array we can keep its values.
2561                         ar.push.apply(ar, _isa(c[i]) ? c[i] : [ c[i] ]);
2562                         ar.push.apply(ar, _isa(b[i]) ? b[i] : [ b[i] ]);
2563                         c[i] = ar;
2564                     }
2565                 }
2566                 else {
2567                     if (_isa(b[i])) {
2568                         ar = [];
2569                         // if c's object is also an array we can keep its values.
2570                         if (_isa(c[i])) {
2571                             ar.push.apply(ar, c[i]);
2572                         }
2573                         ar.push.apply(ar, b[i]);
2574                         c[i] = ar;
2575                     }
2576                     else if (_iso(b[i])) {
2577                         // overwite c's value with an object if it is not already one.
2578                         if (!_iso(c[i])) {
2579                             c[i] = {};
2580                         }
2581                         for (var j in b[i]) {
2582                             c[i][j] = b[i][j];
2583                         }
2584                     }
2585                 }
2586 
2587             }
2588             return c;
2589         },
2590         replace: function (inObj, path, value) {
2591             if (inObj == null) {
2592                 return;
2593             }
2594             var q = inObj, t = q;
2595             path.replace(/([^\.])+/g, function (term, lc, pos, str) {
2596                 var array = term.match(/([^\[0-9]+){1}(\[)([0-9+])/),
2597                     last = pos + term.length >= str.length,
2598                     _getArray = function () {
2599                         return t[array[1]] || (function () {
2600                             t[array[1]] = [];
2601                             return t[array[1]];
2602                         })();
2603                     };
2604 
2605                 if (last) {
2606                     // set term = value on current t, creating term as array if necessary.
2607                     if (array) {
2608                         _getArray()[array[3]] = value;
2609                     }
2610                     else {
2611                         t[term] = value;
2612                     }
2613                 }
2614                 else {
2615                     // set to current t[term], creating t[term] if necessary.
2616                     if (array) {
2617                         var a = _getArray();
2618                         t = a[array[3]] || (function () {
2619                             a[array[3]] = {};
2620                             return a[array[3]];
2621                         })();
2622                     }
2623                     else {
2624                         t = t[term] || (function () {
2625                             t[term] = {};
2626                             return t[term];
2627                         })();
2628                     }
2629                 }
2630             });
2631 
2632             return inObj;
2633         },
2634         //
2635         // chain a list of functions, supplied by [ object, method name, args ], and return on the first
2636         // one that returns the failValue. if none return the failValue, return the successValue.
2637         //
2638         functionChain: function (successValue, failValue, fns) {
2639             for (var i = 0; i < fns.length; i++) {
2640                 var o = fns[i][0][fns[i][1]].apply(fns[i][0], fns[i][2]);
2641                 if (o === failValue) {
2642                     return o;
2643                 }
2644             }
2645             return successValue;
2646         },
2647         // take the given model and expand out any parameters.
2648         // 'functionPrefix' is optional, and if present, helps jsplumb figure out what to do if a value is a Function.
2649         // if you do not provide it, jsplumb will run the given values through any functions it finds, and use the function's
2650         // output as the value in the result. if you do provide the prefix, only functions that are named and have this prefix
2651         // will be executed; other functions will be passed as values to the output.
2652         populate: function (model, values, functionPrefix) {
2653             // for a string, see if it has parameter matches, and if so, try to make the substitutions.
2654             var getValue = function (fromString) {
2655                     var matches = fromString.match(/(\${.*?})/g);
2656                     if (matches != null) {
2657                         for (var i = 0; i < matches.length; i++) {
2658                             var val = values[matches[i].substring(2, matches[i].length - 1)] || "";
2659                             if (val != null) {
2660                                 fromString = fromString.replace(matches[i], val);
2661                             }
2662                         }
2663                     }
2664                     return fromString;
2665                 },
2666             // process one entry.
2667                 _one = function (d) {
2668                     if (d != null) {
2669                         if (_iss(d)) {
2670                             return getValue(d);
2671                         }
2672                         else if (_isf(d) && (functionPrefix == null || (d.name || "").indexOf(functionPrefix) === 0)) {
2673                             return d(values);
2674                         }
2675                         else if (_isa(d)) {
2676                             var r = [];
2677                             for (var i = 0; i < d.length; i++) {
2678                                 r.push(_one(d[i]));
2679                             }
2680                             return r;
2681                         }
2682                         else if (_iso(d)) {
2683                             var s = {};
2684                             for (var j in d) {
2685                                 s[j] = _one(d[j]);
2686                             }
2687                             return s;
2688                         }
2689                         else {
2690                             return d;
2691                         }
2692                     }
2693                 };
2694 
2695             return _one(model);
2696         },
2697         findWithFunction: function (a, f) {
2698             if (a) {
2699                 for (var i = 0; i < a.length; i++) {
2700                     if (f(a[i])) {
2701                         return i;
2702                     }
2703                 }
2704             }
2705             return -1;
2706         },
2707         removeWithFunction: function (a, f) {
2708             var idx = root.jsPlumbUtil.findWithFunction(a, f);
2709             if (idx > -1) {
2710                 a.splice(idx, 1);
2711             }
2712             return idx !== -1;
2713         },
2714         remove: function (l, v) {
2715             var idx = l.indexOf(v);
2716             if (idx > -1) {
2717                 l.splice(idx, 1);
2718             }
2719             return idx !== -1;
2720         },
2721         // TODO support insert index
2722         addWithFunction: function (list, item, hashFunction) {
2723             if (root.jsPlumbUtil.findWithFunction(list, hashFunction) === -1) {
2724                 list.push(item);
2725             }
2726         },
2727         addToList: function (map, key, value, insertAtStart) {
2728             var l = map[key];
2729             if (l == null) {
2730                 l = [];
2731                 map[key] = l;
2732             }
2733             l[insertAtStart ? "unshift" : "push"](value);
2734             return l;
2735         },
2736         suggest : function(list, item, insertAtHead) {
2737             if (list.indexOf(item) === -1) {
2738                 if (insertAtHead) {
2739                     list.unshift(item);
2740                 } else {
2741                     list.push(item);
2742                 }
2743                 return true;
2744             }
2745             return false;
2746         },
2747         //
2748         // extends the given obj (which can be an array) with the given constructor function, prototype functions, and
2749         // class members, any of which may be null.
2750         //
2751         extend: function (child, parent, _protoFn) {
2752             var i;
2753             parent = _isa(parent) ? parent : [ parent ];
2754 
2755             for (i = 0; i < parent.length; i++) {
2756                 for (var j in parent[i].prototype) {
2757                     if (parent[i].prototype.hasOwnProperty(j)) {
2758                         child.prototype[j] = parent[i].prototype[j];
2759                     }
2760                 }
2761             }
2762 
2763             var _makeFn = function (name, protoFn) {
2764                 return function () {
2765                     for (i = 0; i < parent.length; i++) {
2766                         if (parent[i].prototype[name]) {
2767                             parent[i].prototype[name].apply(this, arguments);
2768                         }
2769                     }
2770                     return protoFn.apply(this, arguments);
2771                 };
2772             };
2773 
2774             var _oneSet = function (fns) {
2775                 for (var k in fns) {
2776                     child.prototype[k] = _makeFn(k, fns[k]);
2777                 }
2778             };
2779 
2780             if (arguments.length > 2) {
2781                 for (i = 2; i < arguments.length; i++) {
2782                     _oneSet(arguments[i]);
2783                 }
2784             }
2785 
2786             return child;
2787         },
2788         uuid: function () {
2789             return ('xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
2790                 var r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8);
2791                 return v.toString(16);
2792             }));
2793         },
2794         logEnabled: true,
2795         log: function () {
2796             if (root.jsPlumbUtil.logEnabled && typeof console !== "undefined") {
2797                 try {
2798                     var msg = arguments[arguments.length - 1];
2799                     console.log(msg);
2800                 }
2801                 catch (e) {
2802                 }
2803             }
2804         },
2805 
2806         /**
2807          * Wraps one function with another, creating a placeholder for the
2808          * wrapped function if it was null. this is used to wrap the various
2809          * drag/drop event functions - to allow jsPlumb to be notified of
2810          * important lifecycle events without imposing itself on the user's
2811          * drag/drop functionality.
2812          * @method jsPlumbUtil.wrap
2813          * @param {Function} wrappedFunction original function to wrap; may be null.
2814          * @param {Function} newFunction function to wrap the original with.
2815          * @param {Object} [returnOnThisValue] Optional. Indicates that the wrappedFunction should
2816          * not be executed if the newFunction returns a value matching 'returnOnThisValue'.
2817          * note that this is a simple comparison and only works for primitives right now.
2818          */
2819         wrap: function (wrappedFunction, newFunction, returnOnThisValue) {
2820             wrappedFunction = wrappedFunction || function () {
2821             };
2822             newFunction = newFunction || function () {
2823             };
2824             return function () {
2825                 var r = null;
2826                 try {
2827                     r = newFunction.apply(this, arguments);
2828                 } catch (e) {
2829                     root.jsPlumbUtil.log("jsPlumb function failed : " + e);
2830                 }
2831                 if (returnOnThisValue == null || (r !== returnOnThisValue)) {
2832                     try {
2833                         r = wrappedFunction.apply(this, arguments);
2834                     } catch (e) {
2835                         root.jsPlumbUtil.log("wrapped function failed : " + e);
2836                     }
2837                 }
2838                 return r;
2839             };
2840         }
2841     };
2842 
2843     root.jsPlumbUtil.EventGenerator = function () {
2844         var _listeners = {},
2845             eventsSuspended = false,
2846         // this is a list of events that should re-throw any errors that occur during their dispatch. it is current private.
2847             eventsToDieOn = { "ready": true };
2848 
2849         this.bind = function (event, listener, insertAtStart) {
2850             var _one = function(evt) {
2851                 root.jsPlumbUtil.addToList(_listeners, evt, listener, insertAtStart);
2852                 listener.__jsPlumb = listener.__jsPlumb || {};
2853                 listener.__jsPlumb[root.jsPlumbUtil.uuid()] = evt;
2854             };
2855 
2856             if (typeof event === "string") {
2857                 _one(event);
2858             }
2859             else if (event.length != null) {
2860                 for (var i = 0; i < event.length; i++) {
2861                     _one(event[i]);
2862                 }
2863             }
2864 
2865             return this;
2866         };
2867 
2868         this.fire = function (event, value, originalEvent) {
2869             if (!eventsSuspended && _listeners[event]) {
2870                 var l = _listeners[event].length, i = 0, _gone = false, ret = null;
2871                 if (!this.shouldFireEvent || this.shouldFireEvent(event, value, originalEvent)) {
2872                     while (!_gone && i < l && ret !== false) {
2873                         // doing it this way rather than catching and then possibly re-throwing means that an error propagated by this
2874                         // method will have the whole call stack available in the debugger.
2875                         if (eventsToDieOn[event]) {
2876                             _listeners[event][i].apply(this, [ value, originalEvent]);
2877                         }
2878                         else {
2879                             try {
2880                                 ret = _listeners[event][i].apply(this, [ value, originalEvent ]);
2881                             } catch (e) {
2882                                 root.jsPlumbUtil.log("jsPlumb: fire failed for event " + event + " : " + e);
2883                             }
2884                         }
2885                         i++;
2886                         if (_listeners == null || _listeners[event] == null) {
2887                             _gone = true;
2888                         }
2889                     }
2890                 }
2891             }
2892             return this;
2893         };
2894 
2895         this.unbind = function (eventOrListener, listener) {
2896 
2897             if (arguments.length === 0) {
2898                 _listeners = {};
2899             }
2900             else if (arguments.length === 1) {
2901                 if (typeof eventOrListener === "string") {
2902                     delete _listeners[eventOrListener];
2903                 }
2904                 else if (eventOrListener.__jsPlumb) {
2905                     var evt;
2906                     for (var i in eventOrListener.__jsPlumb) {
2907                         evt = eventOrListener.__jsPlumb[i];
2908                         root.jsPlumbUtil.remove(_listeners[evt] || [], eventOrListener);
2909                     }
2910                 }
2911             }
2912             else if (arguments.length === 2) {
2913                 root.jsPlumbUtil.remove(_listeners[eventOrListener] || [], listener);
2914             }
2915 
2916             return this;
2917         };
2918 
2919         this.getListener = function (forEvent) {
2920             return _listeners[forEvent];
2921         };
2922         this.setSuspendEvents = function (val) {
2923             eventsSuspended = val;
2924         };
2925         this.isSuspendEvents = function () {
2926             return eventsSuspended;
2927         };
2928         this.silently = function(fn) {
2929             this.setSuspendEvents(true);
2930             try {
2931                 fn();
2932             }
2933             catch (e) {
2934                 root.jsPlumbUtil.log("Cannot execute silent function " + e);
2935             }
2936             this.setSuspendEvents(false);
2937         };
2938         this.cleanupListeners = function () {
2939             for (var i in _listeners) {
2940                 _listeners[i] = null;
2941             }
2942         };
2943     };
2944 
2945     root.jsPlumbUtil.EventGenerator.prototype = {
2946         cleanup: function () {
2947             this.cleanupListeners();
2948         }
2949     };
2950 
2951     if (typeof exports !== "undefined") {
2952         exports.jsPlumbUtil = root.jsPlumbUtil;
2953     }
2954 
2955 }).call(typeof window !== 'undefined' ? window : this);
2956 
2957 /*
2958  * jsPlumb Community Edition
2959  *
2960  * Provides a way to visually connect elements on an HTML page, using SVG.
2961  *
2962  * This file contains utility functions that run in browsers only.
2963  *
2964  * Copyright (c) 2010 - 2017 jsPlumb (hello@jsplumbtoolkit.com)
2965  *
2966  * https://jsplumbtoolkit.com
2967  * https://github.com/jsplumb/jsplumb
2968  *
2969  * Dual licensed under the MIT and GPL2 licenses.
2970  */
2971  ;(function() {
2972 
2973   "use strict";
2974 
2975    var root = this;
2976 
2977     root.jsPlumbUtil.matchesSelector = function(el, selector, ctx) {
2978        ctx = ctx || el.parentNode;
2979        var possibles = ctx.querySelectorAll(selector);
2980        for (var i = 0; i < possibles.length; i++) {
2981            if (possibles[i] === el) {
2982                return true;
2983            }
2984        }
2985        return false;
2986    };
2987 
2988     root.jsPlumbUtil.consume = function(e, doNotPreventDefault) {
2989        if (e.stopPropagation) {
2990            e.stopPropagation();
2991        }
2992        else {
2993            e.returnValue = false;
2994        }
2995 
2996        if (!doNotPreventDefault && e.preventDefault){
2997            e.preventDefault();
2998        }
2999    };
3000 
3001    /*
3002     * Function: sizeElement
3003     * Helper to size and position an element. You would typically use
3004     * this when writing your own Connector or Endpoint implementation.
3005     *
3006     * Parameters:
3007     *  x - [int] x position for the element origin
3008     *  y - [int] y position for the element origin
3009     *  w - [int] width of the element
3010     *  h - [int] height of the element
3011     *
3012     */
3013     root.jsPlumbUtil.sizeElement = function(el, x, y, w, h) {
3014        if (el) {
3015            el.style.height = h + "px";
3016            el.height = h;
3017            el.style.width = w + "px";
3018            el.width = w;
3019            el.style.left = x + "px";
3020            el.style.top = y + "px";
3021        }
3022    };
3023 
3024  }).call(typeof window !== 'undefined' ? window : this);
3025 
3026 /*
3027  * jsPlumb
3028  * 
3029  * Title:jsPlumb 2.3.0
3030  * 
3031  * Provides a way to visually connect elements on an HTML page, using SVG.
3032  * 
3033  * This file contains the core code.
3034  *
3035  * Copyright (c) 2010 - 2017 jsPlumb (hello@jsplumbtoolkit.com)
3036  * 
3037  * http://jsplumbtoolkit.com
3038  * http://github.com/sporritt/jsplumb
3039  * 
3040  * Dual licensed under the MIT and GPL2 licenses.
3041  */
3042 (function () {
3043 
3044     "use strict";
3045 
3046     var root = this;
3047     var connectorTypes = [], rendererTypes;
3048 
3049     var _ju = root.jsPlumbUtil,
3050 
3051         /**
3052          * creates a timestamp, using milliseconds since 1970, but as a string.
3053          */
3054         _timestamp = function () {
3055             return "" + (new Date()).getTime();
3056         },
3057 
3058     // helper method to update the hover style whenever it, or paintStyle, changes.
3059     // we use paintStyle as the foundation and merge hoverPaintStyle over the
3060     // top.
3061         _updateHoverStyle = function (component) {
3062             if (component._jsPlumb.paintStyle && component._jsPlumb.hoverPaintStyle) {
3063                 var mergedHoverStyle = {};
3064                 jsPlumb.extend(mergedHoverStyle, component._jsPlumb.paintStyle);
3065                 jsPlumb.extend(mergedHoverStyle, component._jsPlumb.hoverPaintStyle);
3066                 delete component._jsPlumb.hoverPaintStyle;
3067                 // we want the fill of paintStyle to override a gradient, if possible.
3068                 if (mergedHoverStyle.gradient && component._jsPlumb.paintStyle.fill) {
3069                     delete mergedHoverStyle.gradient;
3070                 }
3071                 component._jsPlumb.hoverPaintStyle = mergedHoverStyle;
3072             }
3073         },
3074         events = ["tap", "dbltap", "click", "dblclick", "mouseover", "mouseout", "mousemove", "mousedown", "mouseup", "contextmenu" ],
3075         eventFilters = { "mouseout": "mouseleave", "mouseexit": "mouseleave" },
3076         _updateAttachedElements = function (component, state, timestamp, sourceElement) {
3077             var affectedElements = component.getAttachedElements();
3078             if (affectedElements) {
3079                 for (var i = 0, j = affectedElements.length; i < j; i++) {
3080                     if (!sourceElement || sourceElement !== affectedElements[i]) {
3081                         affectedElements[i].setHover(state, true, timestamp);			// tell the attached elements not to inform their own attached elements.
3082                     }
3083                 }
3084             }
3085         },
3086         _splitType = function (t) {
3087             return t == null ? null : t.split(" ");
3088         },
3089         _mapType = function(map, obj, typeId) {
3090             for (var i in obj) {
3091                 map[i] = typeId;
3092             }
3093         },
3094         _each = function(fn, obj) {
3095             obj = _ju.isArray(obj) || (obj.length != null && !_ju.isString(obj)) ? obj : [ obj ];
3096             for (var i = 0; i < obj.length; i++) {
3097                 try {
3098                     fn.apply(obj[i], [ obj[i] ]);
3099                 }
3100                 catch (e) {
3101                     _ju.log(".each iteration failed : " + e);
3102                 }
3103             }
3104         },
3105         _applyTypes = function (component, params, doNotRepaint) {
3106             if (component.getDefaultType) {
3107                 var td = component.getTypeDescriptor(), map = {};
3108                 var defType = component.getDefaultType();
3109                 var o = _ju.merge({}, defType);
3110                 _mapType(map, defType, "__default");
3111                 for (var i = 0, j = component._jsPlumb.types.length; i < j; i++) {
3112                     var tid = component._jsPlumb.types[i];
3113                     if (tid !== "__default") {
3114                         var _t = component._jsPlumb.instance.getType(tid, td);
3115                         if (_t != null) {
3116                             o = _ju.merge(o, _t, [ "cssClass" ]);
3117                             _mapType(map, _t, tid);
3118                         }
3119                     }
3120                 }
3121 
3122                 if (params) {
3123                     o = _ju.populate(o, params, "_");
3124                 }
3125 
3126                 component.applyType(o, doNotRepaint, map);
3127                 if (!doNotRepaint) {
3128                     component.repaint();
3129                 }
3130             }
3131         },
3132 
3133 // ------------------------------ BEGIN jsPlumbUIComponent --------------------------------------------
3134 
3135         jsPlumbUIComponent = root.jsPlumbUIComponent = function (params) {
3136 
3137             _ju.EventGenerator.apply(this, arguments);
3138 
3139             var self = this,
3140                 a = arguments,
3141                 idPrefix = self.idPrefix,
3142                 id = idPrefix + (new Date()).getTime();
3143 
3144             this._jsPlumb = {
3145                 instance: params._jsPlumb,
3146                 parameters: params.parameters || {},
3147                 paintStyle: null,
3148                 hoverPaintStyle: null,
3149                 paintStyleInUse: null,
3150                 hover: false,
3151                 beforeDetach: params.beforeDetach,
3152                 beforeDrop: params.beforeDrop,
3153                 overlayPlacements: [],
3154                 hoverClass: params.hoverClass || params._jsPlumb.Defaults.HoverClass,
3155                 types: [],
3156                 typeCache:{}
3157             };
3158 
3159             this.cacheTypeItem = function(key, item, typeId) {
3160                 this._jsPlumb.typeCache[typeId] = this._jsPlumb.typeCache[typeId] || {};
3161                 this._jsPlumb.typeCache[typeId][key] = item;
3162             };
3163             this.getCachedTypeItem = function(key, typeId) {
3164                 return this._jsPlumb.typeCache[typeId] ? this._jsPlumb.typeCache[typeId][key] : null;
3165             };
3166 
3167             this.getId = function () {
3168                 return id;
3169             };
3170 
3171 // ----------------------------- default type --------------------------------------------
3172 
3173 
3174             var o = params.overlays || [], oo = {};
3175             if (this.defaultOverlayKeys) {
3176                 for (var i = 0; i < this.defaultOverlayKeys.length; i++) {
3177                     Array.prototype.push.apply(o, this._jsPlumb.instance.Defaults[this.defaultOverlayKeys[i]] || []);
3178                 }
3179 
3180                 for (i = 0; i < o.length; i++) {
3181                     // if a string, convert to object representation so that we can store the typeid on it.
3182                     // also assign an id.
3183                     var fo = jsPlumb.convertToFullOverlaySpec(o[i]);
3184                     oo[fo[1].id] = fo;
3185                 }
3186             }
3187 
3188             var _defaultType = {
3189                 overlays:oo,
3190                 parameters: params.parameters || {},
3191                 scope: params.scope || this._jsPlumb.instance.getDefaultScope()
3192             };
3193             this.getDefaultType = function() {
3194                 return _defaultType;
3195             };
3196             this.appendToDefaultType = function(obj) {
3197                 for (var i in obj) {
3198                     _defaultType[i] = obj[i];
3199                 }
3200             };
3201 
3202 // ----------------------------- end default type --------------------------------------------
3203 
3204             // all components can generate events
3205 
3206             if (params.events) {
3207                 for (var evtName in params.events) {
3208                     self.bind(evtName, params.events[evtName]);
3209                 }
3210             }
3211 
3212             // all components get this clone function.
3213             // TODO issue 116 showed a problem with this - it seems 'a' that is in
3214             // the clone function's scope is shared by all invocations of it, the classic
3215             // JS closure problem.  for now, jsPlumb does a version of this inline where
3216             // it used to call clone.  but it would be nice to find some time to look
3217             // further at this.
3218             this.clone = function () {
3219                 var o = Object.create(this.constructor.prototype);
3220                 this.constructor.apply(o, a);
3221                 return o;
3222             }.bind(this);
3223 
3224             // user can supply a beforeDetach callback, which will be executed before a detach
3225             // is performed; returning false prevents the detach.
3226             this.isDetachAllowed = function (connection) {
3227                 var r = true;
3228                 if (this._jsPlumb.beforeDetach) {
3229                     try {
3230                         r = this._jsPlumb.beforeDetach(connection);
3231                     }
3232                     catch (e) {
3233                         _ju.log("jsPlumb: beforeDetach callback failed", e);
3234                     }
3235                 }
3236                 return r;
3237             };
3238 
3239             // user can supply a beforeDrop callback, which will be executed before a dropped
3240             // connection is confirmed. user can return false to reject connection.
3241             this.isDropAllowed = function (sourceId, targetId, scope, connection, dropEndpoint, source, target) {
3242                 var r = this._jsPlumb.instance.checkCondition("beforeDrop", {
3243                     sourceId: sourceId,
3244                     targetId: targetId,
3245                     scope: scope,
3246                     connection: connection,
3247                     dropEndpoint: dropEndpoint,
3248                     source: source, target: target
3249                 });
3250                 if (this._jsPlumb.beforeDrop) {
3251                     try {
3252                         r = this._jsPlumb.beforeDrop({
3253                             sourceId: sourceId,
3254                             targetId: targetId,
3255                             scope: scope,
3256                             connection: connection,
3257                             dropEndpoint: dropEndpoint,
3258                             source: source, target: target
3259                         });
3260                     }
3261                     catch (e) {
3262                         _ju.log("jsPlumb: beforeDrop callback failed", e);
3263                     }
3264                 }
3265                 return r;
3266             };
3267 
3268             var domListeners = [];
3269 
3270             // sets the component associated with listener events. for instance, an overlay delegates
3271             // its events back to a connector. but if the connector is swapped on the underlying connection,
3272             // then this component must be changed. This is called by setConnector in the Connection class.
3273             this.setListenerComponent = function (c) {
3274                 for (var i = 0; i < domListeners.length; i++) {
3275                     domListeners[i][3] = c;
3276                 }
3277             };
3278 
3279 
3280         };
3281 
3282     var _removeTypeCssHelper = function (component, typeIndex) {
3283         var typeId = component._jsPlumb.types[typeIndex],
3284             type = component._jsPlumb.instance.getType(typeId, component.getTypeDescriptor());
3285 
3286         if (type != null && type.cssClass && component.canvas) {
3287             component._jsPlumb.instance.removeClass(component.canvas, type.cssClass);
3288         }
3289     };
3290 
3291     _ju.extend(root.jsPlumbUIComponent, _ju.EventGenerator, {
3292 
3293         getParameter: function (name) {
3294             return this._jsPlumb.parameters[name];
3295         },
3296 
3297         setParameter: function (name, value) {
3298             this._jsPlumb.parameters[name] = value;
3299         },
3300 
3301         getParameters: function () {
3302             return this._jsPlumb.parameters;
3303         },
3304 
3305         setParameters: function (p) {
3306             this._jsPlumb.parameters = p;
3307         },
3308 
3309         getClass:function() {
3310             return jsPlumb.getClass(this.canvas);
3311         },
3312 
3313         hasClass:function(clazz) {
3314             return jsPlumb.hasClass(this.canvas, clazz);
3315         },
3316 
3317         addClass: function (clazz) {
3318             jsPlumb.addClass(this.canvas, clazz);
3319         },
3320 
3321         removeClass: function (clazz) {
3322             jsPlumb.removeClass(this.canvas, clazz);
3323         },
3324 
3325         updateClasses: function (classesToAdd, classesToRemove) {
3326             jsPlumb.updateClasses(this.canvas, classesToAdd, classesToRemove);
3327         },
3328 
3329         setType: function (typeId, params, doNotRepaint) {
3330             this.clearTypes();
3331             this._jsPlumb.types = _splitType(typeId) || [];
3332             _applyTypes(this, params, doNotRepaint);
3333         },
3334 
3335         getType: function () {
3336             return this._jsPlumb.types;
3337         },
3338 
3339         reapplyTypes: function (params, doNotRepaint) {
3340             _applyTypes(this, params, doNotRepaint);
3341         },
3342 
3343         hasType: function (typeId) {
3344             return this._jsPlumb.types.indexOf(typeId) !== -1;
3345         },
3346 
3347         addType: function (typeId, params, doNotRepaint) {
3348             var t = _splitType(typeId), _cont = false;
3349             if (t != null) {
3350                 for (var i = 0, j = t.length; i < j; i++) {
3351                     if (!this.hasType(t[i])) {
3352                         this._jsPlumb.types.push(t[i]);
3353                         _cont = true;
3354                     }
3355                 }
3356                 if (_cont) {
3357                     _applyTypes(this, params, doNotRepaint);
3358                 }
3359             }
3360         },
3361 
3362         removeType: function (typeId, params, doNotRepaint) {
3363             var t = _splitType(typeId), _cont = false, _one = function (tt) {
3364                 var idx = this._jsPlumb.types.indexOf(tt);
3365                 if (idx !== -1) {
3366                     // remove css class if necessary
3367                     _removeTypeCssHelper(this, idx);
3368                     this._jsPlumb.types.splice(idx, 1);
3369                     return true;
3370                 }
3371                 return false;
3372             }.bind(this);
3373 
3374             if (t != null) {
3375                 for (var i = 0, j = t.length; i < j; i++) {
3376                     _cont = _one(t[i]) || _cont;
3377                 }
3378                 if (_cont) {
3379                     _applyTypes(this, params, doNotRepaint);
3380                 }
3381             }
3382         },
3383         clearTypes: function (params, doNotRepaint) {
3384             var i = this._jsPlumb.types.length;
3385             for (var j = 0; j < i; j++) {
3386                 _removeTypeCssHelper(this, 0);
3387                 this._jsPlumb.types.splice(0, 1);
3388             }
3389             _applyTypes(this, params, doNotRepaint);
3390         },
3391 
3392         toggleType: function (typeId, params, doNotRepaint) {
3393             var t = _splitType(typeId);
3394             if (t != null) {
3395                 for (var i = 0, j = t.length; i < j; i++) {
3396                     var idx = this._jsPlumb.types.indexOf(t[i]);
3397                     if (idx !== -1) {
3398                         _removeTypeCssHelper(this, idx);
3399                         this._jsPlumb.types.splice(idx, 1);
3400                     }
3401                     else {
3402                         this._jsPlumb.types.push(t[i]);
3403                     }
3404                 }
3405 
3406                 _applyTypes(this, params, doNotRepaint);
3407             }
3408         },
3409         applyType: function (t, doNotRepaint) {
3410             this.setPaintStyle(t.paintStyle, doNotRepaint);
3411             this.setHoverPaintStyle(t.hoverPaintStyle, doNotRepaint);
3412             if (t.parameters) {
3413                 for (var i in t.parameters) {
3414                     this.setParameter(i, t.parameters[i]);
3415                 }
3416             }
3417             this._jsPlumb.paintStyleInUse = this.getPaintStyle();
3418         },
3419         setPaintStyle: function (style, doNotRepaint) {
3420             // this._jsPlumb.paintStyle = jsPlumb.extend({}, style);
3421             // TODO figure out if we want components to clone paintStyle so as not to share it.
3422             this._jsPlumb.paintStyle = style;
3423             this._jsPlumb.paintStyleInUse = this._jsPlumb.paintStyle;
3424             _updateHoverStyle(this);
3425             if (!doNotRepaint) {
3426                 this.repaint();
3427             }
3428         },
3429         getPaintStyle: function () {
3430             return this._jsPlumb.paintStyle;
3431         },
3432         setHoverPaintStyle: function (style, doNotRepaint) {
3433             //this._jsPlumb.hoverPaintStyle = jsPlumb.extend({}, style);
3434             // TODO figure out if we want components to clone paintStyle so as not to share it.
3435             this._jsPlumb.hoverPaintStyle = style;
3436             _updateHoverStyle(this);
3437             if (!doNotRepaint) {
3438                 this.repaint();
3439             }
3440         },
3441         getHoverPaintStyle: function () {
3442             return this._jsPlumb.hoverPaintStyle;
3443         },
3444         destroy: function (force) {
3445             if (force || this.typeId == null) {
3446                 this.cleanupListeners(); // this is on EventGenerator
3447                 this.clone = null;
3448                 this._jsPlumb = null;
3449             }
3450         },
3451 
3452         isHover: function () {
3453             return this._jsPlumb.hover;
3454         },
3455 
3456         setHover: function (hover, ignoreAttachedElements, timestamp) {
3457             // while dragging, we ignore these events.  this keeps the UI from flashing and
3458             // swishing and whatevering.
3459             if (this._jsPlumb && !this._jsPlumb.instance.currentlyDragging && !this._jsPlumb.instance.isHoverSuspended()) {
3460 
3461                 this._jsPlumb.hover = hover;
3462                 var method = hover ? "addClass" : "removeClass";
3463 
3464                 if (this.canvas != null) {
3465                     if (this._jsPlumb.instance.hoverClass != null) {
3466                         this._jsPlumb.instance[method](this.canvas, this._jsPlumb.instance.hoverClass);
3467                     }
3468                     if (this._jsPlumb.hoverClass != null) {
3469                         this._jsPlumb.instance[method](this.canvas, this._jsPlumb.hoverClass);
3470                     }
3471                 }
3472                 if (this._jsPlumb.hoverPaintStyle != null) {
3473                     this._jsPlumb.paintStyleInUse = hover ? this._jsPlumb.hoverPaintStyle : this._jsPlumb.paintStyle;
3474                     if (!this._jsPlumb.instance.isSuspendDrawing()) {
3475                         timestamp = timestamp || _timestamp();
3476                         this.repaint({timestamp: timestamp, recalc: false});
3477                     }
3478                 }
3479                 // get the list of other affected elements, if supported by this component.
3480                 // for a connection, its the endpoints.  for an endpoint, its the connections! surprise.
3481                 if (this.getAttachedElements && !ignoreAttachedElements) {
3482                     _updateAttachedElements(this, hover, _timestamp(), this);
3483                 }
3484             }
3485         }
3486     });
3487 
3488 // ------------------------------ END jsPlumbUIComponent --------------------------------------------
3489 
3490     var _jsPlumbInstanceIndex = 0,
3491         getInstanceIndex = function () {
3492             var i = _jsPlumbInstanceIndex + 1;
3493             _jsPlumbInstanceIndex++;
3494             return i;
3495         };
3496 
3497     var jsPlumbInstance = root.jsPlumbInstance = function (_defaults) {
3498 
3499         this.version = "2.4.3";
3500 
3501         if (_defaults) {
3502             jsPlumb.extend(this.Defaults, _defaults);
3503         }
3504 
3505         this.logEnabled = this.Defaults.LogEnabled;
3506         this._connectionTypes = {};
3507         this._endpointTypes = {};
3508 
3509         _ju.EventGenerator.apply(this);
3510 
3511         var _currentInstance = this,
3512             _instanceIndex = getInstanceIndex(),
3513             _bb = _currentInstance.bind,
3514             _initialDefaults = {},
3515             _zoom = 1,
3516             _info = function (el) {
3517                 if (el == null) {
3518                     return null;
3519                 }
3520                 else if (el.nodeType === 3 || el.nodeType === 8) {
3521                     return { el:el, text:true };
3522                 }
3523                 else {
3524                     var _el = _currentInstance.getElement(el);
3525                     return { el: _el, id: (_ju.isString(el) && _el == null) ? el : _getId(_el) };
3526                 }
3527             };
3528 
3529         this.getInstanceIndex = function () {
3530             return _instanceIndex;
3531         };
3532 
3533         this.setZoom = function (z, repaintEverything) {
3534             _zoom = z;
3535             _currentInstance.fire("zoom", _zoom);
3536             if (repaintEverything) {
3537                 _currentInstance.repaintEverything();
3538             }
3539             return true;
3540         };
3541         this.getZoom = function () {
3542             return _zoom;
3543         };
3544 
3545         for (var i in this.Defaults) {
3546             _initialDefaults[i] = this.Defaults[i];
3547         }
3548 
3549         var _container, _containerDelegations = [];
3550         this.unbindContainer = function() {
3551             if (_container != null && _containerDelegations.length > 0) {
3552                 for (var i = 0; i < _containerDelegations.length; i++) {
3553                     _currentInstance.off(_container, _containerDelegations[i][0], _containerDelegations[i][1]);
3554                 }
3555             }
3556         };
3557         this.setContainer = function (c) {
3558 
3559             this.unbindContainer();
3560 
3561             // get container as dom element.
3562             c = this.getElement(c);
3563             // move existing connections and endpoints, if any.
3564             this.select().each(function (conn) {
3565                 conn.moveParent(c);
3566             });
3567             this.selectEndpoints().each(function (ep) {
3568                 ep.moveParent(c);
3569             });
3570 
3571             // set container.
3572             var previousContainer = _container;
3573             _container = c;
3574             _containerDelegations.length = 0;
3575             var eventAliases = {
3576                 "endpointclick":"endpointClick",
3577                 "endpointdblclick":"endpointDblClick"
3578             };
3579 
3580             var _oneDelegateHandler = function (id, e, componentType) {
3581                 var t = e.srcElement || e.target,
3582                     jp = (t && t.parentNode ? t.parentNode._jsPlumb : null) || (t ? t._jsPlumb : null) || (t && t.parentNode && t.parentNode.parentNode ? t.parentNode.parentNode._jsPlumb : null);
3583                 if (jp) {
3584                     jp.fire(id, jp, e);
3585                     var alias = componentType ? eventAliases[componentType + id] || id : id;
3586                     // jsplumb also fires every event coming from components/overlays. That's what the test for `jp.component` is for.
3587                     _currentInstance.fire(alias, jp.component || jp, e);
3588                 }
3589             };
3590 
3591             var _addOneDelegate = function(eventId, selector, fn) {
3592                 _containerDelegations.push([eventId, fn]);
3593                 _currentInstance.on(_container, eventId, selector, fn);
3594             };
3595 
3596             // delegate one event on the container to jsplumb elements. it might be possible to
3597             // abstract this out: each of endpoint, connection and overlay could register themselves with
3598             // jsplumb as "component types" or whatever, and provide a suitable selector. this would be
3599             // done by the renderer (although admittedly from 2.0 onwards we're not supporting vml anymore)
3600             var _oneDelegate = function (id) {
3601                 // connections.
3602                 _addOneDelegate(id, ".jtk-connector", function (e) {
3603                     _oneDelegateHandler(id, e);
3604                 });
3605                 // endpoints. note they can have an enclosing div, or not.
3606                 _addOneDelegate(id, ".jtk-endpoint", function (e) {
3607                     _oneDelegateHandler(id, e, "endpoint");
3608                 });
3609                 // overlays
3610                 _addOneDelegate(id, ".jtk-overlay", function (e) {
3611                     _oneDelegateHandler(id, e);
3612                 });
3613             };
3614 
3615             for (var i = 0; i < events.length; i++) {
3616                 _oneDelegate(events[i]);
3617             }
3618 
3619             // managed elements
3620             for (var elId in managedElements) {
3621                 var el = managedElements[elId].el;
3622                 if (el.parentNode === previousContainer) {
3623                     previousContainer.removeChild(el);
3624                     _container.appendChild(el);
3625                 }
3626             }
3627 
3628         };
3629         this.getContainer = function () {
3630             return _container;
3631         };
3632 
3633         this.bind = function (event, fn) {
3634             if ("ready" === event && initialized) {
3635                 fn();
3636             }
3637             else {
3638                 _bb.apply(_currentInstance, [event, fn]);
3639             }
3640         };
3641 
3642         _currentInstance.importDefaults = function (d) {
3643             for (var i in d) {
3644                 _currentInstance.Defaults[i] = d[i];
3645             }
3646             if (d.Container) {
3647                 _currentInstance.setContainer(d.Container);
3648             }
3649 
3650             return _currentInstance;
3651         };
3652 
3653         _currentInstance.restoreDefaults = function () {
3654             _currentInstance.Defaults = jsPlumb.extend({}, _initialDefaults);
3655             return _currentInstance;
3656         };
3657 
3658         var log = null,
3659             initialized = false,
3660         // TODO remove from window scope
3661             connections = [],
3662         // map of element id -> endpoint lists. an element can have an arbitrary
3663         // number of endpoints on it, and not all of them have to be connected
3664         // to anything.
3665             endpointsByElement = {},
3666             endpointsByUUID = {},
3667             managedElements = {},
3668             offsets = {},
3669             offsetTimestamps = {},
3670             draggableStates = {},
3671             connectionBeingDragged = false,
3672             sizes = [],
3673             _suspendDrawing = false,
3674             _suspendedAt = null,
3675             DEFAULT_SCOPE = this.Defaults.Scope,
3676             _curIdStamp = 1,
3677             _idstamp = function () {
3678                 return "" + _curIdStamp++;
3679             },
3680 
3681         //
3682         // appends an element to some other element, which is calculated as follows:
3683         //
3684         // 1. if Container exists, use that element.
3685         // 2. if the 'parent' parameter exists, use that.
3686         // 3. otherwise just use the root element.
3687         //
3688         //
3689             _appendElement = function (el, parent) {
3690                 if (_container) {
3691                     _container.appendChild(el);
3692                 }
3693                 else if (!parent) {
3694                     this.appendToRoot(el);
3695                 }
3696                 else {
3697                     this.getElement(parent).appendChild(el);
3698                 }
3699             }.bind(this),
3700 
3701         //
3702         // Draws an endpoint and its connections. this is the main entry point into drawing connections as well
3703         // as endpoints, since jsPlumb is endpoint-centric under the hood.
3704         //
3705         // @param element element to draw (of type library specific element object)
3706         // @param ui UI object from current library's event system. optional.
3707         // @param timestamp timestamp for this paint cycle. used to speed things up a little by cutting down the amount of offset calculations we do.
3708         // @param clearEdits defaults to false; indicates that mouse edits for connectors should be cleared
3709         ///
3710             _draw = function (element, ui, timestamp, clearEdits) {
3711 
3712                 if (!_suspendDrawing) {
3713                     var id = _getId(element),
3714                         repaintEls,
3715                         dm = _currentInstance.getDragManager();
3716 
3717                     if (dm) {
3718                         repaintEls = dm.getElementsForDraggable(id);
3719                     }
3720 
3721                     if (timestamp == null) {
3722                         timestamp = _timestamp();
3723                     }
3724 
3725                     // update the offset of everything _before_ we try to draw anything.
3726                     var o = _updateOffset({ elId: id, offset: ui, recalc: false, timestamp: timestamp });
3727 
3728                     if (repaintEls && o && o.o) {
3729                         for (var i in repaintEls) {
3730                             _updateOffset({
3731                                 elId: repaintEls[i].id,
3732                                 offset: {
3733                                     left: o.o.left + repaintEls[i].offset.left,
3734                                     top: o.o.top + repaintEls[i].offset.top
3735                                 },
3736                                 recalc: false,
3737                                 timestamp: timestamp
3738                             });
3739                         }
3740                     }
3741 
3742                     _currentInstance.anchorManager.redraw(id, ui, timestamp, null, clearEdits);
3743 
3744                     if (repaintEls) {
3745                         for (var j in repaintEls) {
3746                             _currentInstance.anchorManager.redraw(repaintEls[j].id, ui, timestamp, repaintEls[j].offset, clearEdits, true);
3747                         }
3748                     }
3749                 }
3750             },
3751 
3752         //
3753         // gets an Endpoint by uuid.
3754         //
3755             _getEndpoint = function (uuid) {
3756                 return endpointsByUUID[uuid];
3757             },
3758 
3759             /**
3760              * inits a draggable if it's not already initialised.
3761              * TODO: somehow abstract this to the adapter, because the concept of "draggable" has no
3762              * place on the server.
3763              */
3764             _initDraggableIfNecessary = function (element, isDraggable, dragOptions, id, fireEvent) {
3765                 // move to DragManager?
3766                 if (!jsPlumb.headless) {
3767                     var _draggable = isDraggable == null ? false : isDraggable;
3768                     if (_draggable) {
3769                         if (jsPlumb.isDragSupported(element, _currentInstance)) {
3770                             var options = dragOptions || _currentInstance.Defaults.DragOptions;
3771                             options = jsPlumb.extend({}, options); // make a copy.
3772                             if (!jsPlumb.isAlreadyDraggable(element, _currentInstance)) {
3773                                 var dragEvent = jsPlumb.dragEvents.drag,
3774                                     stopEvent = jsPlumb.dragEvents.stop,
3775                                     startEvent = jsPlumb.dragEvents.start,
3776                                     _started = false;
3777 
3778                                 _manage(id, element);
3779 
3780                                 options[startEvent] = _ju.wrap(options[startEvent], function () {
3781                                     _currentInstance.setHoverSuspended(true);
3782                                     _currentInstance.select({source: element}).addClass(_currentInstance.elementDraggingClass + " " + _currentInstance.sourceElementDraggingClass, true);
3783                                     _currentInstance.select({target: element}).addClass(_currentInstance.elementDraggingClass + " " + _currentInstance.targetElementDraggingClass, true);
3784                                     _currentInstance.setConnectionBeingDragged(true);
3785                                     if (options.canDrag) {
3786                                         return dragOptions.canDrag();
3787                                     }
3788                                 }, false);
3789 
3790                                 options[dragEvent] = _ju.wrap(options[dragEvent], function () {
3791                                     // TODO: here we could actually use getDragObject, and then compute it ourselves,
3792                                     // since every adapter does the same thing. but i'm not sure why YUI's getDragObject
3793                                     // differs from getUIPosition so much
3794                                     var ui = _currentInstance.getUIPosition(arguments, _currentInstance.getZoom());
3795                                     if (ui != null) {
3796                                         _draw(element, ui, null, true);
3797                                         if (_started) {
3798                                             _currentInstance.addClass(element, "jtk-dragged");
3799                                         }
3800                                         _started = true;
3801                                     }
3802                                 });
3803                                 options[stopEvent] = _ju.wrap(options[stopEvent], function () {
3804                                     var elements = arguments[0].selection, uip;
3805 
3806                                     var _one = function (_e) {
3807                                         if (_e[1] != null) {
3808                                             // run the reported offset through the code that takes parent containers
3809                                             // into account, to adjust if necessary (issue 554)
3810                                             uip = _currentInstance.getUIPosition([{
3811                                                 el:_e[2].el,
3812                                                 pos:[_e[1].left, _e[1].top]
3813                                             }]);
3814                                             _draw(_e[2].el, uip);
3815                                         }
3816                                         _currentInstance.removeClass(_e[0], "jtk-dragged");
3817                                         _currentInstance.select({source: _e[2].el}).removeClass(_currentInstance.elementDraggingClass + " " + _currentInstance.sourceElementDraggingClass, true);
3818                                         _currentInstance.select({target: _e[2].el}).removeClass(_currentInstance.elementDraggingClass + " " + _currentInstance.targetElementDraggingClass, true);
3819                                         _currentInstance.getDragManager().dragEnded(_e[2].el);
3820                                     };
3821 
3822                                     for (var i = 0; i < elements.length; i++) {
3823                                         _one(elements[i]);
3824                                     }
3825 
3826                                     _started = false;
3827                                     _currentInstance.setHoverSuspended(false);
3828                                     _currentInstance.setConnectionBeingDragged(false);
3829                                 });
3830                                 var elId = _getId(element); // need ID
3831                                 draggableStates[elId] = true;
3832                                 var draggable = draggableStates[elId];
3833                                 options.disabled = draggable == null ? false : !draggable;
3834                                 _currentInstance.initDraggable(element, options);
3835                                 _currentInstance.getDragManager().register(element);
3836                                 if (fireEvent) {
3837                                     _currentInstance.fire("elementDraggable", {el:element, options:options});
3838                                 }
3839                             }
3840                             else {
3841                                 // already draggable. attach any start, drag or stop listeners to the current Drag.
3842                                 if (dragOptions.force) {
3843                                     _currentInstance.initDraggable(element, options);
3844                                 }
3845                             }
3846                         }
3847                     }
3848                 }
3849             },
3850 
3851             _scopeMatch = function (e1, e2) {
3852                 var s1 = e1.scope.split(/\s/), s2 = e2.scope.split(/\s/);
3853                 for (var i = 0; i < s1.length; i++) {
3854                     for (var j = 0; j < s2.length; j++) {
3855                         if (s2[j] === s1[i]) {
3856                             return true;
3857                         }
3858                     }
3859                 }
3860 
3861                 return false;
3862             },
3863 
3864         /*
3865          * prepares a final params object that can be passed to _newConnection, taking into account defaults, events, etc.
3866          */
3867             _prepareConnectionParams = function (params, referenceParams) {
3868                 var _p = jsPlumb.extend({ }, params);
3869                 if (referenceParams) {
3870                     jsPlumb.extend(_p, referenceParams);
3871                 }
3872 
3873                 // hotwire endpoints passed as source or target to sourceEndpoint/targetEndpoint, respectively.
3874                 if (_p.source) {
3875                     if (_p.source.endpoint) {
3876                         _p.sourceEndpoint = _p.source;
3877                     }
3878                     else {
3879                         _p.source = _currentInstance.getElement(_p.source);
3880                     }
3881                 }
3882                 if (_p.target) {
3883                     if (_p.target.endpoint) {
3884                         _p.targetEndpoint = _p.target;
3885                     }
3886                     else {
3887                         _p.target = _currentInstance.getElement(_p.target);
3888                     }
3889                 }
3890 
3891                 // test for endpoint uuids to connect
3892                 if (params.uuids) {
3893                     _p.sourceEndpoint = _getEndpoint(params.uuids[0]);
3894                     _p.targetEndpoint = _getEndpoint(params.uuids[1]);
3895                 }
3896 
3897                 // now ensure that if we do have Endpoints already, they're not full.
3898                 // source:
3899                 if (_p.sourceEndpoint && _p.sourceEndpoint.isFull()) {
3900                     _ju.log(_currentInstance, "could not add connection; source endpoint is full");
3901                     return;
3902                 }
3903 
3904                 // target:
3905                 if (_p.targetEndpoint && _p.targetEndpoint.isFull()) {
3906                     _ju.log(_currentInstance, "could not add connection; target endpoint is full");
3907                     return;
3908                 }
3909 
3910                 // if source endpoint mandates connection type and nothing specified in our params, use it.
3911                 if (!_p.type && _p.sourceEndpoint) {
3912                     _p.type = _p.sourceEndpoint.connectionType;
3913                 }
3914 
3915                 // copy in any connectorOverlays that were specified on the source endpoint.
3916                 // it doesnt copy target endpoint overlays.  i'm not sure if we want it to or not.
3917                 if (_p.sourceEndpoint && _p.sourceEndpoint.connectorOverlays) {
3918                     _p.overlays = _p.overlays || [];
3919                     for (var i = 0, j = _p.sourceEndpoint.connectorOverlays.length; i < j; i++) {
3920                         _p.overlays.push(_p.sourceEndpoint.connectorOverlays[i]);
3921                     }
3922                 }
3923 
3924                 // scope
3925                 if (_p.sourceEndpoint && _p.sourceEndpoint.scope) {
3926                     _p.scope = _p.sourceEndpoint.scope;
3927                 }
3928 
3929                 // pointer events
3930                 if (!_p["pointer-events"] && _p.sourceEndpoint && _p.sourceEndpoint.connectorPointerEvents) {
3931                     _p["pointer-events"] = _p.sourceEndpoint.connectorPointerEvents;
3932                 }
3933 
3934                 var _mergeOverrides = function (def, values) {
3935                     var m = jsPlumb.extend({}, def);
3936                     for (var i in values) {
3937                         if (values[i]) {
3938                             m[i] = values[i];
3939                         }
3940                     }
3941                     return m;
3942                 };
3943 
3944                 var _addEndpoint = function (el, def, idx) {
3945                     return _currentInstance.addEndpoint(el, _mergeOverrides(def, {
3946                         anchor: _p.anchors ? _p.anchors[idx] : _p.anchor,
3947                         endpoint: _p.endpoints ? _p.endpoints[idx] : _p.endpoint,
3948                         paintStyle: _p.endpointStyles ? _p.endpointStyles[idx] : _p.endpointStyle,
3949                         hoverPaintStyle: _p.endpointHoverStyles ? _p.endpointHoverStyles[idx] : _p.endpointHoverStyle
3950                     }));
3951                 };
3952 
3953                 // check for makeSource/makeTarget specs.
3954 
3955                 var _oneElementDef = function (type, idx, defs, matchType) {
3956                     if (_p[type] && !_p[type].endpoint && !_p[type + "Endpoint"] && !_p.newConnection) {
3957                         var tid = _getId(_p[type]), tep = defs[tid];
3958 
3959                         tep = tep ? tep[matchType] : null;
3960 
3961                         if (tep) {
3962                             // if not enabled, return.
3963                             if (!tep.enabled) {
3964                                 return false;
3965                             }
3966                             var newEndpoint = tep.endpoint != null && tep.endpoint._jsPlumb ? tep.endpoint : _addEndpoint(_p[type], tep.def, idx);
3967                             if (newEndpoint.isFull()) {
3968                                 return false;
3969                             }
3970                             _p[type + "Endpoint"] = newEndpoint;
3971                             if (!_p.scope && tep.def.scope) {
3972                                 _p.scope = tep.def.scope;
3973                             } // provide scope if not already provided and endpoint def has one.
3974                             newEndpoint.setDeleteOnEmpty(true);
3975                             if (tep.uniqueEndpoint) {
3976                                 if (!tep.endpoint) {
3977                                     tep.endpoint = newEndpoint;
3978                                     newEndpoint.setDeleteOnEmpty(false);
3979                                 }
3980                                 else {
3981                                     newEndpoint.finalEndpoint = tep.endpoint;
3982                                 }
3983                             }
3984                         }
3985                     }
3986                 };
3987 
3988                 if (_oneElementDef("source", 0, this.sourceEndpointDefinitions, _p.type || "default") === false) {
3989                     return;
3990                 }
3991                 if (_oneElementDef("target", 1, this.targetEndpointDefinitions, _p.type || "default") === false) {
3992                     return;
3993                 }
3994 
3995                 // last, ensure scopes match
3996                 if (_p.sourceEndpoint && _p.targetEndpoint) {
3997                     if (!_scopeMatch(_p.sourceEndpoint, _p.targetEndpoint)) {
3998                         _p = null;
3999                     }
4000                 }
4001 
4002                 return _p;
4003             }.bind(_currentInstance),
4004 
4005             _newConnection = function (params) {
4006                 var connectionFunc = _currentInstance.Defaults.ConnectionType || _currentInstance.getDefaultConnectionType();
4007 
4008                 params._jsPlumb = _currentInstance;
4009                 params.newConnection = _newConnection;
4010                 params.newEndpoint = _newEndpoint;
4011                 params.endpointsByUUID = endpointsByUUID;
4012                 params.endpointsByElement = endpointsByElement;
4013                 params.finaliseConnection = _finaliseConnection;
4014                 params.id = "con_" + _idstamp();
4015                 var con = new connectionFunc(params);
4016 
4017                 // if the connection is draggable, then maybe we need to tell the target endpoint to init the
4018                 // dragging code. it won't run again if it already configured to be draggable.
4019                 if (con.isDetachable()) {
4020                     con.endpoints[0].initDraggable("_jsPlumbSource");
4021                     con.endpoints[1].initDraggable("_jsPlumbTarget");
4022                 }
4023 
4024                 return con;
4025             },
4026 
4027         //
4028         // adds the connection to the backing model, fires an event if necessary and then redraws
4029         //
4030             _finaliseConnection = _currentInstance.finaliseConnection = function (jpc, params, originalEvent, doInformAnchorManager) {
4031                 params = params || {};
4032                 // add to list of connections (by scope).
4033                 if (!jpc.suspendedEndpoint) {
4034                     connections.push(jpc);
4035                 }
4036 
4037                 jpc.pending = null;
4038 
4039                 // turn off isTemporarySource on the source endpoint (only viable on first draw)
4040                 jpc.endpoints[0].isTemporarySource = false;
4041 
4042                 // always inform the anchor manager
4043                 // except that if jpc has a suspended endpoint it's not true to say the
4044                 // connection is new; it has just (possibly) moved. the question is whether
4045                 // to make that call here or in the anchor manager.  i think perhaps here.
4046                 if (doInformAnchorManager !== false) {
4047                     _currentInstance.anchorManager.newConnection(jpc);
4048                 }
4049 
4050                 // force a paint
4051                 _draw(jpc.source);
4052 
4053                 // fire an event
4054                 if (!params.doNotFireConnectionEvent && params.fireEvent !== false) {
4055 
4056                     var eventArgs = {
4057                         connection: jpc,
4058                         source: jpc.source, target: jpc.target,
4059                         sourceId: jpc.sourceId, targetId: jpc.targetId,
4060                         sourceEndpoint: jpc.endpoints[0], targetEndpoint: jpc.endpoints[1]
4061                     };
4062 
4063                     _currentInstance.fire("connection", eventArgs, originalEvent);
4064                 }
4065             },
4066 
4067         /*
4068          factory method to prepare a new endpoint.  this should always be used instead of creating Endpoints
4069          manually, since this method attaches event listeners and an id.
4070          */
4071             _newEndpoint = function (params, id) {
4072                 var endpointFunc = _currentInstance.Defaults.EndpointType || jsPlumb.Endpoint;
4073                 var _p = jsPlumb.extend({}, params);
4074                 _p._jsPlumb = _currentInstance;
4075                 _p.newConnection = _newConnection;
4076                 _p.newEndpoint = _newEndpoint;
4077                 _p.endpointsByUUID = endpointsByUUID;
4078                 _p.endpointsByElement = endpointsByElement;
4079                 _p.fireDetachEvent = fireDetachEvent;
4080                 _p.elementId = id || _getId(_p.source);
4081                 var ep = new endpointFunc(_p);
4082                 ep.id = "ep_" + _idstamp();
4083                 _manage(_p.elementId, _p.source);
4084 
4085                 if (!jsPlumb.headless) {
4086                     _currentInstance.getDragManager().endpointAdded(_p.source, id);
4087                 }
4088 
4089                 return ep;
4090             },
4091 
4092         /*
4093          * performs the given function operation on all the connections found
4094          * for the given element id; this means we find all the endpoints for
4095          * the given element, and then for each endpoint find the connectors
4096          * connected to it. then we pass each connection in to the given
4097          * function.
4098          */
4099             _operation = function (elId, func, endpointFunc) {
4100                 var endpoints = endpointsByElement[elId];
4101                 if (endpoints && endpoints.length) {
4102                     for (var i = 0, ii = endpoints.length; i < ii; i++) {
4103                         for (var j = 0, jj = endpoints[i].connections.length; j < jj; j++) {
4104                             var retVal = func(endpoints[i].connections[j]);
4105                             // if the function passed in returns true, we exit.
4106                             // most functions return false.
4107                             if (retVal) {
4108                                 return;
4109                             }
4110                         }
4111                         if (endpointFunc) {
4112                             endpointFunc(endpoints[i]);
4113                         }
4114                     }
4115                 }
4116             },
4117 
4118             _setDraggable = function (element, draggable) {
4119                 return jsPlumb.each(element, function (el) {
4120                     if (_currentInstance.isDragSupported(el)) {
4121                         draggableStates[_currentInstance.getAttribute(el, "id")] = draggable;
4122                         _currentInstance.setElementDraggable(el, draggable);
4123                     }
4124                 });
4125             },
4126         /*
4127          * private method to do the business of hiding/showing.
4128          *
4129          * @param el
4130          *            either Id of the element in question or a library specific
4131          *            object for the element.
4132          * @param state
4133          *            String specifying a value for the css 'display' property
4134          *            ('block' or 'none').
4135          */
4136             _setVisible = function (el, state, alsoChangeEndpoints) {
4137                 state = state === "block";
4138                 var endpointFunc = null;
4139                 if (alsoChangeEndpoints) {
4140                     endpointFunc = function (ep) {
4141                         ep.setVisible(state, true, true);
4142                     };
4143                 }
4144                 var info = _info(el);
4145                 _operation(info.id, function (jpc) {
4146                     if (state && alsoChangeEndpoints) {
4147                         // this test is necessary because this functionality is new, and i wanted to maintain backwards compatibility.
4148                         // this block will only set a connection to be visible if the other endpoint in the connection is also visible.
4149                         var oidx = jpc.sourceId === info.id ? 1 : 0;
4150                         if (jpc.endpoints[oidx].isVisible()) {
4151                             jpc.setVisible(true);
4152                         }
4153                     }
4154                     else { // the default behaviour for show, and what always happens for hide, is to just set the visibility without getting clever.
4155                         jpc.setVisible(state);
4156                     }
4157                 }, endpointFunc);
4158             },
4159         /*
4160          * toggles the draggable state of the given element(s).
4161          * el is either an id, or an element object, or a list of ids/element objects.
4162          */
4163             _toggleDraggable = function (el) {
4164                 var state;
4165                 jsPlumb.each(el, function (el) {
4166                     var elId = _currentInstance.getAttribute(el, "id");
4167                     state = draggableStates[elId] == null ? false : draggableStates[elId];
4168                     state = !state;
4169                     draggableStates[elId] = state;
4170                     _currentInstance.setDraggable(el, state);
4171                     return state;
4172                 }.bind(this));
4173                 return state;
4174             },
4175             /**
4176              * private method to do the business of toggling hiding/showing.
4177              */
4178             _toggleVisible = function (elId, changeEndpoints) {
4179                 var endpointFunc = null;
4180                 if (changeEndpoints) {
4181                     endpointFunc = function (ep) {
4182                         var state = ep.isVisible();
4183                         ep.setVisible(!state);
4184                     };
4185                 }
4186                 _operation(elId, function (jpc) {
4187                     var state = jpc.isVisible();
4188                     jpc.setVisible(!state);
4189                 }, endpointFunc);
4190             },
4191 
4192         // TODO comparison performance
4193             _getCachedData = function (elId) {
4194                 var o = offsets[elId];
4195                 if (!o) {
4196                     return _updateOffset({elId: elId});
4197                 }
4198                 else {
4199                     return {o: o, s: sizes[elId]};
4200                 }
4201             },
4202 
4203             /**
4204              * gets an id for the given element, creating and setting one if
4205              * necessary.  the id is of the form
4206              *
4207              *    jsPlumb_<instance index>_<index in instance>
4208              *
4209              * where "index in instance" is a monotonically increasing integer that starts at 0,
4210              * for each instance.  this method is used not only to assign ids to elements that do not
4211              * have them but also to connections and endpoints.
4212              */
4213             _getId = function (element, uuid, doNotCreateIfNotFound) {
4214                 if (_ju.isString(element)) {
4215                     return element;
4216                 }
4217                 if (element == null) {
4218                     return null;
4219                 }
4220                 var id = _currentInstance.getAttribute(element, "id");
4221                 if (!id || id === "undefined") {
4222                     // check if fixed uuid parameter is given
4223                     if (arguments.length === 2 && arguments[1] !== undefined) {
4224                         id = uuid;
4225                     }
4226                     else if (arguments.length === 1 || (arguments.length === 3 && !arguments[2])) {
4227                         id = "jsPlumb_" + _instanceIndex + "_" + _idstamp();
4228                     }
4229 
4230                     if (!doNotCreateIfNotFound) {
4231                         _currentInstance.setAttribute(element, "id", id);
4232                     }
4233                 }
4234                 return id;
4235             };
4236 
4237         this.setConnectionBeingDragged = function (v) {
4238             connectionBeingDragged = v;
4239         };
4240         this.isConnectionBeingDragged = function () {
4241             return connectionBeingDragged;
4242         };
4243 
4244         /**
4245          * Returns a map of all the elements this jsPlumbInstance is currently managing.
4246          * @returns {Object} Map of [id-> {el, endpoint[], connection, position}] information.
4247          */
4248         this.getManagedElements = function() {
4249             return managedElements;
4250         };
4251 
4252         this.connectorClass = "jtk-connector";
4253         this.connectorOutlineClass = "jtk-connector-outline";
4254         this.editableConnectorClass = "jtk-connector-editable";
4255         this.connectedClass = "jtk-connected";
4256         this.hoverClass = "jtk-hover";
4257         this.endpointClass = "jtk-endpoint";
4258         this.endpointConnectedClass = "jtk-endpoint-connected";
4259         this.endpointFullClass = "jtk-endpoint-full";
4260         this.endpointDropAllowedClass = "jtk-endpoint-drop-allowed";
4261         this.endpointDropForbiddenClass = "jtk-endpoint-drop-forbidden";
4262         this.overlayClass = "jtk-overlay";
4263         this.draggingClass = "jtk-dragging";
4264         this.elementDraggingClass = "jtk-element-dragging";
4265         this.sourceElementDraggingClass = "jtk-source-element-dragging";
4266         this.targetElementDraggingClass = "jtk-target-element-dragging";
4267         this.endpointAnchorClassPrefix = "jtk-endpoint-anchor";
4268         this.hoverSourceClass = "jtk-source-hover";
4269         this.hoverTargetClass = "jtk-target-hover";
4270         this.dragSelectClass = "jtk-drag-select";
4271 
4272         this.Anchors = {};
4273         this.Connectors = {  "svg": {} };
4274         this.Endpoints = { "svg": {} };
4275         this.Overlays = { "svg": {} } ;
4276         this.ConnectorRenderers = {};
4277         this.SVG = "svg";
4278 
4279 // --------------------------- jsPlumbInstance public API ---------------------------------------------------------
4280 
4281 
4282         this.addEndpoint = function (el, params, referenceParams) {
4283             referenceParams = referenceParams || {};
4284             var p = jsPlumb.extend({}, referenceParams);
4285             jsPlumb.extend(p, params);
4286             p.endpoint = p.endpoint || _currentInstance.Defaults.Endpoint;
4287             p.paintStyle = p.paintStyle || _currentInstance.Defaults.EndpointStyle;
4288 
4289             var results = [],
4290                 inputs = (_ju.isArray(el) || (el.length != null && !_ju.isString(el))) ? el : [ el ];
4291 
4292             for (var i = 0, j = inputs.length; i < j; i++) {
4293                 p.source = _currentInstance.getElement(inputs[i]);
4294                 _ensureContainer(p.source);
4295 
4296                 var id = _getId(p.source), e = _newEndpoint(p, id);
4297 
4298                 // ensure element is managed.
4299                 var myOffset = _manage(id, p.source).info.o;
4300                 _ju.addToList(endpointsByElement, id, e);
4301 
4302                 if (!_suspendDrawing) {
4303                     e.paint({
4304                         anchorLoc: e.anchor.compute({ xy: [ myOffset.left, myOffset.top ], wh: sizes[id], element: e, timestamp: _suspendedAt }),
4305                         timestamp: _suspendedAt
4306                     });
4307                 }
4308 
4309                 results.push(e);
4310             }
4311 
4312             return results.length === 1 ? results[0] : results;
4313         };
4314 
4315         this.addEndpoints = function (el, endpoints, referenceParams) {
4316             var results = [];
4317             for (var i = 0, j = endpoints.length; i < j; i++) {
4318                 var e = _currentInstance.addEndpoint(el, endpoints[i], referenceParams);
4319                 if (_ju.isArray(e)) {
4320                     Array.prototype.push.apply(results, e);
4321                 }
4322                 else {
4323                     results.push(e);
4324                 }
4325             }
4326             return results;
4327         };
4328 
4329         this.animate = function (el, properties, options) {
4330             if (!this.animationSupported) {
4331                 return false;
4332             }
4333 
4334             options = options || {};
4335             var del = _currentInstance.getElement(el),
4336                 id = _getId(del),
4337                 stepFunction = jsPlumb.animEvents.step,
4338                 completeFunction = jsPlumb.animEvents.complete;
4339 
4340             options[stepFunction] = _ju.wrap(options[stepFunction], function () {
4341                 _currentInstance.revalidate(id);
4342             });
4343 
4344             // onComplete repaints, just to make sure everything looks good at the end of the animation.
4345             options[completeFunction] = _ju.wrap(options[completeFunction], function () {
4346                 _currentInstance.revalidate(id);
4347             });
4348 
4349             _currentInstance.doAnimate(del, properties, options);
4350         };
4351 
4352         /**
4353          * checks for a listener for the given condition, executing it if found, passing in the given value.
4354          * condition listeners would have been attached using "bind" (which is, you could argue, now overloaded, since
4355          * firing click events etc is a bit different to what this does).  i thought about adding a "bindCondition"
4356          * or something, but decided against it, for the sake of simplicity. jsPlumb will never fire one of these
4357          * condition events anyway.
4358          */
4359         this.checkCondition = function (conditionName, args) {
4360             var l = _currentInstance.getListener(conditionName),
4361                 r = true;
4362 
4363             if (l && l.length > 0) {
4364                 var values = Array.prototype.slice.call(arguments, 1);
4365                 try {
4366                     for (var i = 0, j = l.length; i < j; i++) {
4367                         r = r && l[i].apply(l[i], values);
4368                     }
4369                 }
4370                 catch (e) {
4371                     _ju.log(_currentInstance, "cannot check condition [" + conditionName + "]" + e);
4372                 }
4373             }
4374             return r;
4375         };
4376 
4377         this.connect = function (params, referenceParams) {
4378             // prepare a final set of parameters to create connection with
4379             var _p = _prepareConnectionParams(params, referenceParams), jpc;
4380             // TODO probably a nicer return value if the connection was not made.  _prepareConnectionParams
4381             // will return null (and log something) if either endpoint was full.  what would be nicer is to
4382             // create a dedicated 'error' object.
4383             if (_p) {
4384                 if (_p.source == null && _p.sourceEndpoint == null) {
4385                     _ju.log("Cannot establish connection - source does not exist");
4386                     return;
4387                 }
4388                 if (_p.target == null && _p.targetEndpoint == null) {
4389                     _ju.log("Cannot establish connection - target does not exist");
4390                     return;
4391                 }
4392                 _ensureContainer(_p.source);
4393                 // create the connection.  it is not yet registered
4394                 jpc = _newConnection(_p);
4395                 // now add it the model, fire an event, and redraw
4396                 _finaliseConnection(jpc, _p);
4397             }
4398             return jpc;
4399         };
4400 
4401         var stTypes = [
4402             { el: "source", elId: "sourceId", epDefs: "sourceEndpointDefinitions" },
4403             { el: "target", elId: "targetId", epDefs: "targetEndpointDefinitions" }
4404         ];
4405 
4406         var _set = function (c, el, idx, doNotRepaint) {
4407             var ep, _st = stTypes[idx], cId = c[_st.elId], cEl = c[_st.el], sid, sep,
4408                 oldEndpoint = c.endpoints[idx];
4409 
4410             var evtParams = {
4411                 index: idx,
4412                 originalSourceId: idx === 0 ? cId : c.sourceId,
4413                 newSourceId: c.sourceId,
4414                 originalTargetId: idx === 1 ? cId : c.targetId,
4415                 newTargetId: c.targetId,
4416                 connection: c
4417             };
4418 
4419             if (el.constructor === jsPlumb.Endpoint) {
4420                 ep = el;
4421                 ep.addConnection(c);
4422                 el = ep.element;
4423             }
4424             else {
4425                 sid = _getId(el);
4426                 sep = this[_st.epDefs][sid];
4427 
4428                 if (sid === c[_st.elId]) {
4429                     ep = null; // dont change source/target if the element is already the one given.
4430                 }
4431                 else if (sep) {
4432                     for (var t in sep) {
4433                         if (!sep[t].enabled) {
4434                             return;
4435                         }
4436                         ep = sep[t].endpoint != null && sep[t].endpoint._jsPlumb ? sep[t].endpoint : this.addEndpoint(el, sep[t].def);
4437                         if (sep[t].uniqueEndpoint) {
4438                             sep[t].endpoint = ep;
4439                         }
4440                         ep.addConnection(c);
4441                     }
4442                 }
4443                 else {
4444                     ep = c.makeEndpoint(idx === 0, el, sid);
4445                 }
4446             }
4447 
4448             if (ep != null) {
4449                 oldEndpoint.detachFromConnection(c);
4450                 c.endpoints[idx] = ep;
4451                 c[_st.el] = ep.element;
4452                 c[_st.elId] = ep.elementId;
4453                 evtParams[idx === 0 ? "newSourceId" : "newTargetId"] = ep.elementId;
4454 
4455                 fireMoveEvent(evtParams);
4456 
4457                 if (!doNotRepaint) {
4458                     c.repaint();
4459                 }
4460             }
4461 
4462             evtParams.element = el;
4463             return evtParams;
4464 
4465         }.bind(this);
4466 
4467         this.setSource = function (connection, el, doNotRepaint) {
4468             var p = _set(connection, el, 0, doNotRepaint);
4469             this.anchorManager.sourceChanged(p.originalSourceId, p.newSourceId, connection, p.el);
4470         };
4471         this.setTarget = function (connection, el, doNotRepaint) {
4472             var p = _set(connection, el, 1, doNotRepaint);
4473             this.anchorManager.updateOtherEndpoint(p.originalSourceId, p.originalTargetId, p.newTargetId, connection);
4474         };
4475 
4476         this.deleteEndpoint = function (object, dontUpdateHover, deleteAttachedObjects) {
4477             var endpoint = (typeof object === "string") ? endpointsByUUID[object] : object;
4478             if (endpoint) {
4479                 _currentInstance.deleteObject({ endpoint: endpoint, dontUpdateHover: dontUpdateHover, deleteAttachedObjects:deleteAttachedObjects });
4480             }
4481             return _currentInstance;
4482         };
4483 
4484         this.deleteEveryEndpoint = function () {
4485             var _is = _currentInstance.setSuspendDrawing(true);
4486             for (var id in endpointsByElement) {
4487                 var endpoints = endpointsByElement[id];
4488                 if (endpoints && endpoints.length) {
4489                     for (var i = 0, j = endpoints.length; i < j; i++) {
4490                         _currentInstance.deleteEndpoint(endpoints[i], true);
4491                     }
4492                 }
4493             }
4494             endpointsByElement = {};
4495             managedElements = {};
4496             endpointsByUUID = {};
4497             offsets = {};
4498             offsetTimestamps = {};
4499             _currentInstance.anchorManager.reset();
4500             var dm = _currentInstance.getDragManager();
4501             if (dm) {
4502                 dm.reset();
4503             }
4504             if (!_is) {
4505                 _currentInstance.setSuspendDrawing(false);
4506             }
4507             return _currentInstance;
4508         };
4509 
4510         var fireDetachEvent = function (jpc, doFireEvent, originalEvent) {
4511             // may have been given a connection, or in special cases, an object
4512             var connType = _currentInstance.Defaults.ConnectionType || _currentInstance.getDefaultConnectionType(),
4513                 argIsConnection = jpc.constructor === connType,
4514                 params = argIsConnection ? {
4515                     connection: jpc,
4516                     source: jpc.source, target: jpc.target,
4517                     sourceId: jpc.sourceId, targetId: jpc.targetId,
4518                     sourceEndpoint: jpc.endpoints[0], targetEndpoint: jpc.endpoints[1]
4519                 } : jpc;
4520 
4521             if (doFireEvent) {
4522                 _currentInstance.fire("connectionDetached", params, originalEvent);
4523             }
4524 
4525             // always fire this. used by internal jsplumb stuff.
4526             _currentInstance.fire("internal.connectionDetached", params, originalEvent);
4527 
4528             _currentInstance.anchorManager.connectionDetached(params);
4529         };
4530 
4531         var fireMoveEvent = _currentInstance.fireMoveEvent = function (params, evt) {
4532             _currentInstance.fire("connectionMoved", params, evt);
4533         };
4534 
4535         this.unregisterEndpoint = function (endpoint) {
4536             if (endpoint._jsPlumb.uuid) {
4537                 endpointsByUUID[endpoint._jsPlumb.uuid] = null;
4538             }
4539             _currentInstance.anchorManager.deleteEndpoint(endpoint);
4540             // TODO at least replace this with a removeWithFunction call.
4541             for (var e in endpointsByElement) {
4542                 var endpoints = endpointsByElement[e];
4543                 if (endpoints) {
4544                     var newEndpoints = [];
4545                     for (var i = 0, j = endpoints.length; i < j; i++) {
4546                         if (endpoints[i] !== endpoint) {
4547                             newEndpoints.push(endpoints[i]);
4548                         }
4549                     }
4550 
4551                     endpointsByElement[e] = newEndpoints;
4552                 }
4553                 if (endpointsByElement[e].length < 1) {
4554                     delete endpointsByElement[e];
4555                 }
4556             }
4557         };
4558 
4559         var IS_DETACH_ALLOWED = "isDetachAllowed";
4560         var BEFORE_DETACH = "beforeDetach";
4561         var CHECK_CONDITION = "checkCondition";
4562 
4563         /**
4564          * Deletes a Connection.
4565          * @method deleteConnection
4566          * @param connection Connection to delete
4567          * @param {Object} [params] Optional delete parameters
4568          * @param {Boolean} [params.doNotFireEvent=false] If true, a connection detached event will not be fired. Otherwise one will.
4569          * @param {Boolean} [params.force=false] If true, the connection will be deleted even if a beforeDetach interceptor tries to stop the deletion.
4570          * @returns {Boolean} True if the connection was deleted, false otherwise.
4571          */
4572         this.deleteConnection = function(connection, params) {
4573 
4574             if (connection != null) {
4575                 params = params || {};
4576 
4577                 if (params.force || _ju.functionChain(true, false, [
4578                     [ connection.endpoints[0], IS_DETACH_ALLOWED, [ connection ] ],
4579                     [ connection.endpoints[1], IS_DETACH_ALLOWED, [ connection ] ],
4580                     [ connection, IS_DETACH_ALLOWED, [ connection ] ],
4581                     [ _currentInstance, CHECK_CONDITION, [ BEFORE_DETACH, connection ] ]
4582                 ])) {
4583 
4584                     connection.setHover(false);
4585                     fireDetachEvent(connection, !connection.pending && params.fireEvent !== false, params.originalEvent);
4586 
4587                     connection.endpoints[0].detachFromConnection(connection);
4588                     connection.endpoints[1].detachFromConnection(connection);
4589                     _ju.removeWithFunction(connections, function (_c) {
4590                         return connection.id === _c.id;
4591                     });
4592 
4593                     connection.cleanup();
4594                     connection.destroy();
4595                     return true;
4596                 }
4597             }
4598             return false;
4599         };
4600 
4601         /**
4602          * Remove all Connections from all elements, but leaves Endpoints in place ((unless a connection is set to auto delete its Endpoints).
4603          * @method deleteEveryConnection
4604          * @param {Object} [params] optional params object for the call
4605          * @param {Boolean} [params.fireEvent=true] Whether or not to fire detach events
4606          * @param {Boolean} [params.forceDetach=false] If true, this call will ignore any `beforeDetach` interceptors.
4607          * @returns {Number} The number of connections that were deleted.
4608          */
4609         this.deleteEveryConnection = function (params) {
4610             params = params || {};
4611             var count = connections.length, deletedCount = 0;
4612             _currentInstance.batch(function () {
4613                 for (var i = 0; i < count; i++) {
4614                     deletedCount += _currentInstance.deleteConnection(connections[0], params) ? 1 : 0;
4615                 }
4616             });
4617             return deletedCount;
4618         };
4619 
4620         /**
4621          * Removes all an element's Connections.
4622          * @method deleteConnectionsForElement
4623          * @param {Object} el Either the id of the element, or a selector for the element.
4624          * @param {Object} [params] Optional parameters.
4625          * @param {Boolean} [params.fireEvent=true] Whether or not to fire the detach event.
4626          * @param {Boolean} [params.forceDetach=false] If true, this call will ignore any `beforeDetach` interceptors.
4627          * @return {jsPlumbInstance} The current jsPlumb instance.
4628          */
4629         this.deleteConnectionsForElement = function (el, params) {
4630             params = params || {};
4631             el = _currentInstance.getElement(el);
4632             var id = _getId(el), endpoints = endpointsByElement[id];
4633             if (endpoints && endpoints.length) {
4634                 for (var i = 0, j = endpoints.length; i < j; i++) {
4635                     endpoints[i].deleteEveryConnection(params);
4636                 }
4637             }
4638             return _currentInstance;
4639         };
4640 
4641         /// not public.  but of course its exposed. how to change this.
4642         this.deleteObject = function (params) {
4643             var result = {
4644                     endpoints: {},
4645                     connections: {},
4646                     endpointCount: 0,
4647                     connectionCount: 0
4648                 },
4649                 deleteAttachedObjects = params.deleteAttachedObjects !== false;
4650 
4651             var unravelConnection = function (connection) {
4652                 if (connection != null && result.connections[connection.id] == null) {
4653                     if (!params.dontUpdateHover && connection._jsPlumb != null) {
4654                         connection.setHover(false);
4655                     }
4656                     result.connections[connection.id] = connection;
4657                     result.connectionCount++;
4658                 }
4659             };
4660             var unravelEndpoint = function (endpoint) {
4661                 if (endpoint != null && result.endpoints[endpoint.id] == null) {
4662                     if (!params.dontUpdateHover && endpoint._jsPlumb != null) {
4663                         endpoint.setHover(false);
4664                     }
4665                     result.endpoints[endpoint.id] = endpoint;
4666                     result.endpointCount++;
4667 
4668                     if (deleteAttachedObjects) {
4669                         for (var i = 0; i < endpoint.connections.length; i++) {
4670                             var c = endpoint.connections[i];
4671                             unravelConnection(c);
4672                         }
4673                     }
4674                 }
4675             };
4676 
4677             if (params.connection) {
4678                 unravelConnection(params.connection);
4679             }
4680             else {
4681                 unravelEndpoint(params.endpoint);
4682             }
4683 
4684             // loop through connections
4685             for (var i in result.connections) {
4686                 var c = result.connections[i];
4687                 if (c._jsPlumb) {
4688                     _ju.removeWithFunction(connections, function (_c) {
4689                         return c.id === _c.id;
4690                     });
4691 
4692                     fireDetachEvent(c, params.fireEvent === false ? false : !c.pending, params.originalEvent);
4693                     var doNotCleanup = params.deleteAttachedObjects == null ? null : !params.deleteAttachedObjects;
4694 
4695                     c.endpoints[0].detachFromConnection(c, null, doNotCleanup);
4696                     c.endpoints[1].detachFromConnection(c, null, doNotCleanup);
4697 
4698                     c.cleanup(true);
4699                     c.destroy(true);
4700                 }
4701             }
4702 
4703             // loop through endpoints
4704             for (var j in result.endpoints) {
4705                 var e = result.endpoints[j];
4706                 if (e._jsPlumb) {
4707                     _currentInstance.unregisterEndpoint(e);
4708                     // FIRE some endpoint deleted event?
4709                     e.cleanup(true);
4710                     e.destroy(true);
4711                 }
4712             }
4713 
4714             return result;
4715         };
4716 
4717         this.draggable = function (el, options) {
4718             var info;
4719             _each(function(_el) {
4720                  info = _info(_el);
4721                 if (info.el) {
4722                     _initDraggableIfNecessary(info.el, true, options, info.id, true);
4723                 }
4724             }, el);
4725             return _currentInstance;
4726         };
4727 
4728         this.droppable = function(el, options) {
4729             var info;
4730             options = options || {};
4731             options.allowLoopback = false;
4732             _each(function(_el) {
4733                 info = _info(_el);
4734                 if (info.el) {
4735                     _currentInstance.initDroppable(info.el, options);
4736                 }
4737             }, el);
4738             return _currentInstance;
4739         };
4740 
4741         // helpers for select/selectEndpoints
4742         var _setOperation = function (list, func, args, selector) {
4743                 for (var i = 0, j = list.length; i < j; i++) {
4744                     list[i][func].apply(list[i], args);
4745                 }
4746                 return selector(list);
4747             },
4748             _getOperation = function (list, func, args) {
4749                 var out = [];
4750                 for (var i = 0, j = list.length; i < j; i++) {
4751                     out.push([ list[i][func].apply(list[i], args), list[i] ]);
4752                 }
4753                 return out;
4754             },
4755             setter = function (list, func, selector) {
4756                 return function () {
4757                     return _setOperation(list, func, arguments, selector);
4758                 };
4759             },
4760             getter = function (list, func) {
4761                 return function () {
4762                     return _getOperation(list, func, arguments);
4763                 };
4764             },
4765             prepareList = function (input, doNotGetIds) {
4766                 var r = [];
4767                 if (input) {
4768                     if (typeof input === 'string') {
4769                         if (input === "*") {
4770                             return input;
4771                         }
4772                         r.push(input);
4773                     }
4774                     else {
4775                         if (doNotGetIds) {
4776                             r = input;
4777                         }
4778                         else {
4779                             if (input.length) {
4780                                 for (var i = 0, j = input.length; i < j; i++) {
4781                                     r.push(_info(input[i]).id);
4782                                 }
4783                             }
4784                             else {
4785                                 r.push(_info(input).id);
4786                             }
4787                         }
4788                     }
4789                 }
4790                 return r;
4791             },
4792             filterList = function (list, value, missingIsFalse) {
4793                 if (list === "*") {
4794                     return true;
4795                 }
4796                 return list.length > 0 ? list.indexOf(value) !== -1 : !missingIsFalse;
4797             };
4798 
4799         // get some connections, specifying source/target/scope
4800         this.getConnections = function (options, flat) {
4801             if (!options) {
4802                 options = {};
4803             } else if (options.constructor === String) {
4804                 options = { "scope": options };
4805             }
4806             var scope = options.scope || _currentInstance.getDefaultScope(),
4807                 scopes = prepareList(scope, true),
4808                 sources = prepareList(options.source),
4809                 targets = prepareList(options.target),
4810                 results = (!flat && scopes.length > 1) ? {} : [],
4811                 _addOne = function (scope, obj) {
4812                     if (!flat && scopes.length > 1) {
4813                         var ss = results[scope];
4814                         if (ss == null) {
4815                             ss = results[scope] = [];
4816                         }
4817                         ss.push(obj);
4818                     } else {
4819                         results.push(obj);
4820                     }
4821                 };
4822 
4823             for (var j = 0, jj = connections.length; j < jj; j++) {
4824                 var c = connections[j],
4825                     sourceId = c.proxies && c.proxies[0] ? c.proxies[0].originalEp.elementId : c.sourceId,
4826                     targetId = c.proxies && c.proxies[1] ? c.proxies[1].originalEp.elementId : c.targetId;
4827 
4828                 if (filterList(scopes, c.scope) && filterList(sources, sourceId) && filterList(targets, targetId)) {
4829                     _addOne(c.scope, c);
4830                 }
4831             }
4832 
4833             return results;
4834         };
4835 
4836         var _curryEach = function (list, executor) {
4837                 return function (f) {
4838                     for (var i = 0, ii = list.length; i < ii; i++) {
4839                         f(list[i]);
4840                     }
4841                     return executor(list);
4842                 };
4843             },
4844             _curryGet = function (list) {
4845                 return function (idx) {
4846                     return list[idx];
4847                 };
4848             };
4849 
4850         var _makeCommonSelectHandler = function (list, executor) {
4851             var out = {
4852                     length: list.length,
4853                     each: _curryEach(list, executor),
4854                     get: _curryGet(list)
4855                 },
4856                 setters = ["setHover", "removeAllOverlays", "setLabel", "addClass", "addOverlay", "removeOverlay",
4857                     "removeOverlays", "showOverlay", "hideOverlay", "showOverlays", "hideOverlays", "setPaintStyle",
4858                     "setHoverPaintStyle", "setSuspendEvents", "setParameter", "setParameters", "setVisible",
4859                     "repaint", "addType", "toggleType", "removeType", "removeClass", "setType", "bind", "unbind" ],
4860 
4861                 getters = ["getLabel", "getOverlay", "isHover", "getParameter", "getParameters", "getPaintStyle",
4862                     "getHoverPaintStyle", "isVisible", "hasType", "getType", "isSuspendEvents" ],
4863                 i, ii;
4864 
4865             for (i = 0, ii = setters.length; i < ii; i++) {
4866                 out[setters[i]] = setter(list, setters[i], executor);
4867             }
4868 
4869             for (i = 0, ii = getters.length; i < ii; i++) {
4870                 out[getters[i]] = getter(list, getters[i]);
4871             }
4872 
4873             return out;
4874         };
4875 
4876         var _makeConnectionSelectHandler = function (list) {
4877             var common = _makeCommonSelectHandler(list, _makeConnectionSelectHandler);
4878             return jsPlumb.extend(common, {
4879                 // setters
4880                 setDetachable: setter(list, "setDetachable", _makeConnectionSelectHandler),
4881                 setReattach: setter(list, "setReattach", _makeConnectionSelectHandler),
4882                 setConnector: setter(list, "setConnector", _makeConnectionSelectHandler),
4883                 delete: function () {
4884                     for (var i = 0, ii = list.length; i < ii; i++) {
4885                         _currentInstance.deleteConnection(list[i]);
4886                     }
4887                 },
4888                 // getters
4889                 isDetachable: getter(list, "isDetachable"),
4890                 isReattach: getter(list, "isReattach")
4891             });
4892         };
4893 
4894         var _makeEndpointSelectHandler = function (list) {
4895             var common = _makeCommonSelectHandler(list, _makeEndpointSelectHandler);
4896             return jsPlumb.extend(common, {
4897                 setEnabled: setter(list, "setEnabled", _makeEndpointSelectHandler),
4898                 setAnchor: setter(list, "setAnchor", _makeEndpointSelectHandler),
4899                 isEnabled: getter(list, "isEnabled"),
4900                 deleteEveryConnection: function () {
4901                     for (var i = 0, ii = list.length; i < ii; i++) {
4902                         list[i].deleteEveryConnection();
4903                     }
4904                 },
4905                 "delete": function () {
4906                     for (var i = 0, ii = list.length; i < ii; i++) {
4907                         _currentInstance.deleteEndpoint(list[i]);
4908                     }
4909                 }
4910             });
4911         };
4912 
4913         this.select = function (params) {
4914             params = params || {};
4915             params.scope = params.scope || "*";
4916             return _makeConnectionSelectHandler(params.connections || _currentInstance.getConnections(params, true));
4917         };
4918 
4919         this.selectEndpoints = function (params) {
4920             params = params || {};
4921             params.scope = params.scope || "*";
4922             var noElementFilters = !params.element && !params.source && !params.target,
4923                 elements = noElementFilters ? "*" : prepareList(params.element),
4924                 sources = noElementFilters ? "*" : prepareList(params.source),
4925                 targets = noElementFilters ? "*" : prepareList(params.target),
4926                 scopes = prepareList(params.scope, true);
4927 
4928             var ep = [];
4929 
4930             for (var el in endpointsByElement) {
4931                 var either = filterList(elements, el, true),
4932                     source = filterList(sources, el, true),
4933                     sourceMatchExact = sources !== "*",
4934                     target = filterList(targets, el, true),
4935                     targetMatchExact = targets !== "*";
4936 
4937                 // if they requested 'either' then just match scope. otherwise if they requested 'source' (not as a wildcard) then we have to match only endpoints that have isSource set to to true, and the same thing with isTarget.
4938                 if (either || source || target) {
4939                     inner:
4940                         for (var i = 0, ii = endpointsByElement[el].length; i < ii; i++) {
4941                             var _ep = endpointsByElement[el][i];
4942                             if (filterList(scopes, _ep.scope, true)) {
4943 
4944                                 var noMatchSource = (sourceMatchExact && sources.length > 0 && !_ep.isSource),
4945                                     noMatchTarget = (targetMatchExact && targets.length > 0 && !_ep.isTarget);
4946 
4947                                 if (noMatchSource || noMatchTarget) {
4948                                     continue inner;
4949                                 }
4950 
4951                                 ep.push(_ep);
4952                             }
4953                         }
4954                 }
4955             }
4956 
4957             return _makeEndpointSelectHandler(ep);
4958         };
4959 
4960         // get all connections managed by the instance of jsplumb.
4961         this.getAllConnections = function () {
4962             return connections;
4963         };
4964         this.getDefaultScope = function () {
4965             return DEFAULT_SCOPE;
4966         };
4967         // get an endpoint by uuid.
4968         this.getEndpoint = _getEndpoint;
4969         /**
4970          * Gets the list of Endpoints for a given element.
4971          * @method getEndpoints
4972          * @param {String|Element|Selector} el The element to get endpoints for.
4973          * @return {Endpoint[]} An array of Endpoints for the specified element.
4974          */
4975         this.getEndpoints = function (el) {
4976             return endpointsByElement[_info(el).id] || [];
4977         };
4978         // gets the default endpoint type. used when subclassing. see wiki.
4979         this.getDefaultEndpointType = function () {
4980             return jsPlumb.Endpoint;
4981         };
4982         // gets the default connection type. used when subclassing.  see wiki.
4983         this.getDefaultConnectionType = function () {
4984             return jsPlumb.Connection;
4985         };
4986         /*
4987          * Gets an element's id, creating one if necessary. really only exposed
4988          * for the lib-specific functionality to access; would be better to pass
4989          * the current instance into the lib-specific code (even though this is
4990          * a static call. i just don't want to expose it to the public API).
4991          */
4992         this.getId = _getId;
4993 
4994         this.appendElement = _appendElement;
4995 
4996         var _hoverSuspended = false;
4997         this.isHoverSuspended = function () {
4998             return _hoverSuspended;
4999         };
5000         this.setHoverSuspended = function (s) {
5001             _hoverSuspended = s;
5002         };
5003 
5004         // set an element's connections to be hidden
5005         this.hide = function (el, changeEndpoints) {
5006             _setVisible(el, "none", changeEndpoints);
5007             return _currentInstance;
5008         };
5009 
5010         // exposed for other objects to use to get a unique id.
5011         this.idstamp = _idstamp;
5012 
5013         this.connectorsInitialized = false;
5014         this.registerConnectorType = function (connector, name) {
5015             connectorTypes.push([connector, name]);
5016         };
5017 
5018         // ensure that, if the current container exists, it is a DOM element and not a selector.
5019         // if it does not exist and `candidate` is supplied, the offset parent of that element will be set as the Container.
5020         // this is used to do a better default behaviour for the case that the user has not set a container:
5021         // addEndpoint, makeSource, makeTarget and connect all call this method with the offsetParent of the
5022         // element in question (for connect it is the source element). So if no container is set, it is inferred
5023         // to be the offsetParent of the first element the user tries to connect.
5024         var _ensureContainer = function (candidate) {
5025             if (!_container && candidate) {
5026                 var can = _currentInstance.getElement(candidate);
5027                 if (can.offsetParent) {
5028                     _currentInstance.setContainer(can.offsetParent);
5029                 }
5030             }
5031         };
5032 
5033         var _getContainerFromDefaults = function () {
5034             if (_currentInstance.Defaults.Container) {
5035                 _currentInstance.setContainer(_currentInstance.Defaults.Container);
5036             }
5037         };
5038 
5039         // check if a given element is managed or not. if not, add to our map. if drawing is not suspended then
5040         // we'll also stash its dimensions; otherwise we'll do this in a lazy way through updateOffset.
5041         var _manage = _currentInstance.manage = function (id, element, _transient) {
5042             if (!managedElements[id]) {
5043                 managedElements[id] = {
5044                     el: element,
5045                     endpoints: [],
5046                     connections: []
5047                 };
5048 
5049                 managedElements[id].info = _updateOffset({ elId: id, timestamp: _suspendedAt });
5050                 if (!_transient) {
5051                     _currentInstance.fire("manageElement", { id:id, info:managedElements[id].info, el:element });
5052                 }
5053             }
5054 
5055             return managedElements[id];
5056         };
5057 
5058         var _unmanage = function(id) {
5059             if (managedElements[id]) {
5060                 delete managedElements[id];
5061                 _currentInstance.fire("unmanageElement", id);
5062             }
5063         };
5064 
5065         /**
5066          * updates the offset and size for a given element, and stores the
5067          * values. if 'offset' is not null we use that (it would have been
5068          * passed in from a drag call) because it's faster; but if it is null,
5069          * or if 'recalc' is true in order to force a recalculation, we get the current values.
5070          */
5071         var _updateOffset = this.updateOffset = function (params) {
5072 
5073             var timestamp = params.timestamp, recalc = params.recalc, offset = params.offset, elId = params.elId, s;
5074             if (_suspendDrawing && !timestamp) {
5075                 timestamp = _suspendedAt;
5076             }
5077             if (!recalc) {
5078                 if (timestamp && timestamp === offsetTimestamps[elId]) {
5079                     return {o: params.offset || offsets[elId], s: sizes[elId]};
5080                 }
5081             }
5082             if (recalc || (!offset && offsets[elId] == null)) { // if forced repaint or no offset available, we recalculate.
5083 
5084                 // get the current size and offset, and store them
5085                 s = managedElements[elId] ? managedElements[elId].el : null;
5086                 if (s != null) {
5087                     sizes[elId] = _currentInstance.getSize(s);
5088                     offsets[elId] = _currentInstance.getOffset(s);
5089                     offsetTimestamps[elId] = timestamp;
5090                 }
5091             } else {
5092                 offsets[elId] = offset || offsets[elId];
5093                 if (sizes[elId] == null) {
5094                     s = managedElements[elId].el;
5095                     if (s != null) {
5096                         sizes[elId] = _currentInstance.getSize(s);
5097                     }
5098                 }
5099                 offsetTimestamps[elId] = timestamp;
5100             }
5101 
5102             if (offsets[elId] && !offsets[elId].right) {
5103                 offsets[elId].right = offsets[elId].left + sizes[elId][0];
5104                 offsets[elId].bottom = offsets[elId].top + sizes[elId][1];
5105                 offsets[elId].width = sizes[elId][0];
5106                 offsets[elId].height = sizes[elId][1];
5107                 offsets[elId].centerx = offsets[elId].left + (offsets[elId].width / 2);
5108                 offsets[elId].centery = offsets[elId].top + (offsets[elId].height / 2);
5109             }
5110 
5111             return {o: offsets[elId], s: sizes[elId]};
5112         };
5113 
5114         /**
5115          * callback from the current library to tell us to prepare ourselves (attach
5116          * mouse listeners etc; can't do that until the library has provided a bind method)
5117          */
5118         this.init = function () {
5119             rendererTypes = root.jsPlumb.getRenderModes();
5120 
5121             var _oneType = function (renderer, name, fn) {
5122                 root.jsPlumb.Connectors[renderer][name] = function () {
5123                     fn.apply(this, arguments);
5124                     root.jsPlumb.ConnectorRenderers[renderer].apply(this, arguments);
5125                 };
5126                 _ju.extend(root.jsPlumb.Connectors[renderer][name], [ fn, root.jsPlumb.ConnectorRenderers[renderer]]);
5127             };
5128 
5129             if (!root.jsPlumb.connectorsInitialized) {
5130                 for (var i = 0; i < connectorTypes.length; i++) {
5131                     for (var j = 0; j < rendererTypes.length; j++) {
5132                         _oneType(rendererTypes[j], connectorTypes[i][1], connectorTypes[i][0]);
5133                     }
5134 
5135                 }
5136                 root.jsPlumb.connectorsInitialized = true;
5137             }
5138 
5139             if (!initialized) {
5140                 _getContainerFromDefaults();
5141                 _currentInstance.anchorManager = new root.jsPlumb.AnchorManager({jsPlumbInstance: _currentInstance});
5142                 initialized = true;
5143                 _currentInstance.fire("ready", _currentInstance);
5144             }
5145         }.bind(this);
5146 
5147         this.log = log;
5148         this.jsPlumbUIComponent = jsPlumbUIComponent;
5149 
5150         /*
5151          * Creates an anchor with the given params.
5152          *
5153          *
5154          * Returns: The newly created Anchor.
5155          * Throws: an error if a named anchor was not found.
5156          */
5157         this.makeAnchor = function () {
5158             var pp, _a = function (t, p) {
5159                 if (root.jsPlumb.Anchors[t]) {
5160                     return new root.jsPlumb.Anchors[t](p);
5161                 }
5162                 if (!_currentInstance.Defaults.DoNotThrowErrors) {
5163                     throw { msg: "jsPlumb: unknown anchor type '" + t + "'" };
5164                 }
5165             };
5166             if (arguments.length === 0) {
5167                 return null;
5168             }
5169             var specimen = arguments[0], elementId = arguments[1], jsPlumbInstance = arguments[2], newAnchor = null;
5170             // if it appears to be an anchor already...
5171             if (specimen.compute && specimen.getOrientation) {
5172                 return specimen;
5173             }  //TODO hazy here about whether it should be added or is already added somehow.
5174             // is it the name of an anchor type?
5175             else if (typeof specimen === "string") {
5176                 newAnchor = _a(arguments[0], {elementId: elementId, jsPlumbInstance: _currentInstance});
5177             }
5178             // is it an array? it will be one of:
5179             // an array of [spec, params] - this defines a single anchor, which may be dynamic, but has parameters.
5180             // an array of arrays - this defines some dynamic anchors
5181             // an array of numbers - this defines a single anchor.
5182             else if (_ju.isArray(specimen)) {
5183                 if (_ju.isArray(specimen[0]) || _ju.isString(specimen[0])) {
5184                     // if [spec, params] format
5185                     if (specimen.length === 2 && _ju.isObject(specimen[1])) {
5186                         // if first arg is a string, its a named anchor with params
5187                         if (_ju.isString(specimen[0])) {
5188                             pp = root.jsPlumb.extend({elementId: elementId, jsPlumbInstance: _currentInstance}, specimen[1]);
5189                             newAnchor = _a(specimen[0], pp);
5190                         }
5191                         // otherwise first arg is array, second is params. we treat as a dynamic anchor, which is fine
5192                         // even if the first arg has only one entry. you could argue all anchors should be implicitly dynamic in fact.
5193                         else {
5194                             pp = root.jsPlumb.extend({elementId: elementId, jsPlumbInstance: _currentInstance, anchors: specimen[0]}, specimen[1]);
5195                             newAnchor = new root.jsPlumb.DynamicAnchor(pp);
5196                         }
5197                     }
5198                     else {
5199                         newAnchor = new jsPlumb.DynamicAnchor({anchors: specimen, selector: null, elementId: elementId, jsPlumbInstance: _currentInstance});
5200                     }
5201 
5202                 }
5203                 else {
5204                     var anchorParams = {
5205                         x: specimen[0], y: specimen[1],
5206                         orientation: (specimen.length >= 4) ? [ specimen[2], specimen[3] ] : [0, 0],
5207                         offsets: (specimen.length >= 6) ? [ specimen[4], specimen[5] ] : [ 0, 0 ],
5208                         elementId: elementId,
5209                         jsPlumbInstance: _currentInstance,
5210                         cssClass: specimen.length === 7 ? specimen[6] : null
5211                     };
5212                     newAnchor = new root.jsPlumb.Anchor(anchorParams);
5213                     newAnchor.clone = function () {
5214                         return new root.jsPlumb.Anchor(anchorParams);
5215                     };
5216                 }
5217             }
5218 
5219             if (!newAnchor.id) {
5220                 newAnchor.id = "anchor_" + _idstamp();
5221             }
5222             return newAnchor;
5223         };
5224 
5225         /**
5226          * makes a list of anchors from the given list of types or coords, eg
5227          * ["TopCenter", "RightMiddle", "BottomCenter", [0, 1, -1, -1] ]
5228          */
5229         this.makeAnchors = function (types, elementId, jsPlumbInstance) {
5230             var r = [];
5231             for (var i = 0, ii = types.length; i < ii; i++) {
5232                 if (typeof types[i] === "string") {
5233                     r.push(root.jsPlumb.Anchors[types[i]]({elementId: elementId, jsPlumbInstance: jsPlumbInstance}));
5234                 }
5235                 else if (_ju.isArray(types[i])) {
5236                     r.push(_currentInstance.makeAnchor(types[i], elementId, jsPlumbInstance));
5237                 }
5238             }
5239             return r;
5240         };
5241 
5242         /**
5243          * Makes a dynamic anchor from the given list of anchors (which may be in shorthand notation as strings or dimension arrays, or Anchor
5244          * objects themselves) and the given, optional, anchorSelector function (jsPlumb uses a default if this is not provided; most people will
5245          * not need to provide this - i think).
5246          */
5247         this.makeDynamicAnchor = function (anchors, anchorSelector) {
5248             return new root.jsPlumb.DynamicAnchor({anchors: anchors, selector: anchorSelector, elementId: null, jsPlumbInstance: _currentInstance});
5249         };
5250 
5251 // --------------------- makeSource/makeTarget ---------------------------------------------- 
5252 
5253         this.targetEndpointDefinitions = {};
5254         this.sourceEndpointDefinitions = {};
5255 
5256         var selectorFilter = function (evt, _el, selector, _instance, negate) {
5257             var t = evt.target || evt.srcElement, ok = false,
5258                 sel = _instance.getSelector(_el, selector);
5259             for (var j = 0; j < sel.length; j++) {
5260                 if (sel[j] === t) {
5261                     ok = true;
5262                     break;
5263                 }
5264             }
5265             return negate ? !ok : ok;
5266         };
5267 
5268         var _makeElementDropHandler = function (elInfo, p, dropOptions, isSource, isTarget) {
5269             var proxyComponent = new jsPlumbUIComponent(p);
5270             var _drop = p._jsPlumb.EndpointDropHandler({
5271                 jsPlumb: _currentInstance,
5272                 enabled: function () {
5273                     return elInfo.def.enabled;
5274                 },
5275                 isFull: function () {
5276                     var targetCount = _currentInstance.select({target: elInfo.id}).length;
5277                     return elInfo.def.maxConnections > 0 && targetCount >= elInfo.def.maxConnections;
5278                 },
5279                 element: elInfo.el,
5280                 elementId: elInfo.id,
5281                 isSource: isSource,
5282                 isTarget: isTarget,
5283                 addClass: function (clazz) {
5284                     _currentInstance.addClass(elInfo.el, clazz);
5285                 },
5286                 removeClass: function (clazz) {
5287                     _currentInstance.removeClass(elInfo.el, clazz);
5288                 },
5289                 onDrop: function (jpc) {
5290                     var source = jpc.endpoints[0];
5291                     source.anchor.locked = false;
5292                 },
5293                 isDropAllowed: function () {
5294                     return proxyComponent.isDropAllowed.apply(proxyComponent, arguments);
5295                 },
5296                 isRedrop:function(jpc) {
5297                     return (jpc.suspendedElement != null && jpc.suspendedEndpoint != null && jpc.suspendedEndpoint.element === elInfo.el);
5298                 },
5299                 getEndpoint: function (jpc) {
5300 
5301                     // make a new Endpoint for the target, or get it from the cache if uniqueEndpoint
5302                     // is set. if its a redrop the new endpoint will be immediately cleaned up.
5303 
5304                     var newEndpoint = elInfo.def.endpoint;
5305 
5306                     // if no cached endpoint, or there was one but it has been cleaned up
5307                     // (ie. detached), create a new one
5308                     if (newEndpoint == null || newEndpoint._jsPlumb == null) {
5309                         var eps = _currentInstance.deriveEndpointAndAnchorSpec(jpc.getType().join(" "), true);
5310                         var pp = eps.endpoints ? root.jsPlumb.extend(p, {
5311                             endpoint:elInfo.def.def.endpoint || eps.endpoints[1]
5312                         }) :p;
5313                         if (eps.anchors) {
5314                             pp = root.jsPlumb.extend(pp, {
5315                                 anchor:elInfo.def.def.anchor || eps.anchors[1]
5316                             });
5317                         }
5318                         newEndpoint = _currentInstance.addEndpoint(elInfo.el, pp);
5319                         newEndpoint._mtNew = true;
5320                     }
5321 
5322                     if (p.uniqueEndpoint) {
5323                         elInfo.def.endpoint = newEndpoint;
5324                     }
5325 
5326                     newEndpoint.setDeleteOnEmpty(true);
5327 
5328                     // if connection is detachable, init the new endpoint to be draggable, to support that happening.
5329                     if (jpc.isDetachable()) {
5330                         newEndpoint.initDraggable();
5331                     }
5332 
5333                     // if the anchor has a 'positionFinder' set, then delegate to that function to find
5334                     // out where to locate the anchor.
5335                     if (newEndpoint.anchor.positionFinder != null) {
5336                         var dropPosition = _currentInstance.getUIPosition(arguments, _currentInstance.getZoom()),
5337                             elPosition = _currentInstance.getOffset(elInfo.el),
5338                             elSize = _currentInstance.getSize(elInfo.el),
5339                             ap = dropPosition == null ? [0,0] : newEndpoint.anchor.positionFinder(dropPosition, elPosition, elSize, newEndpoint.anchor.constructorParams);
5340 
5341                         newEndpoint.anchor.x = ap[0];
5342                         newEndpoint.anchor.y = ap[1];
5343                         // now figure an orientation for it..kind of hard to know what to do actually. probably the best thing i can do is to
5344                         // support specifying an orientation in the anchor's spec. if one is not supplied then i will make the orientation
5345                         // be what will cause the most natural link to the source: it will be pointing at the source, but it needs to be
5346                         // specified in one axis only, and so how to make that choice? i think i will use whichever axis is the one in which
5347                         // the target is furthest away from the source.
5348                     }
5349 
5350                     return newEndpoint;
5351                 },
5352                 maybeCleanup: function (ep) {
5353                     if (ep._mtNew && ep.connections.length === 0) {
5354                         _currentInstance.deleteObject({endpoint: ep});
5355                     }
5356                     else {
5357                         delete ep._mtNew;
5358                     }
5359                 }
5360             });
5361 
5362             // wrap drop events as needed and initialise droppable
5363             var dropEvent = root.jsPlumb.dragEvents.drop;
5364             dropOptions.scope = dropOptions.scope || (p.scope || _currentInstance.Defaults.Scope);
5365             dropOptions[dropEvent] = _ju.wrap(dropOptions[dropEvent], _drop, true);
5366 
5367             // if target, return true from the over event. this will cause katavorio to stop setting drops to hover
5368             // if multipleDrop is set to false.
5369             if (isTarget) {
5370                 dropOptions[root.jsPlumb.dragEvents.over] = function () { return true; };
5371             }
5372 
5373             // vanilla jsplumb only
5374             if (p.allowLoopback === false) {
5375                 dropOptions.canDrop = function (_drag) {
5376                     var de = _drag.getDragElement()._jsPlumbRelatedElement;
5377                     return de !== elInfo.el;
5378                 };
5379             }
5380             _currentInstance.initDroppable(elInfo.el, dropOptions, "internal");
5381 
5382             return _drop;
5383 
5384         };
5385 
5386         // see API docs
5387         this.makeTarget = function (el, params, referenceParams) {
5388 
5389             // put jsplumb ref into params without altering the params passed in
5390             var p = root.jsPlumb.extend({_jsPlumb: this}, referenceParams);
5391             root.jsPlumb.extend(p, params);
5392 
5393             var maxConnections = p.maxConnections || -1,
5394 
5395                 _doOne = function (el) {
5396 
5397                     // get the element's id and store the endpoint definition for it.  jsPlumb.connect calls will look for one of these,
5398                     // and use the endpoint definition if found.
5399                     // decode the info for this element (id and element)
5400                     var elInfo = _info(el),
5401                         elid = elInfo.id,
5402                         dropOptions = root.jsPlumb.extend({}, p.dropOptions || {}),
5403                         type = p.connectionType || "default";
5404 
5405                     this.targetEndpointDefinitions[elid] = this.targetEndpointDefinitions[elid] || {};
5406 
5407                     _ensureContainer(elid);
5408 
5409                     // if this is a group and the user has not mandated a rank, set to -1 so that Nodes takes
5410                     // precedence.
5411                     if (elInfo.el._isJsPlumbGroup && dropOptions.rank == null) {
5412                         dropOptions.rank = -1;
5413                     }
5414 
5415                     // store the definition
5416                     var _def = {
5417                         def: root.jsPlumb.extend({}, p),
5418                         uniqueEndpoint: p.uniqueEndpoint,
5419                         maxConnections: maxConnections,
5420                         enabled: true
5421                     };
5422                     elInfo.def = _def;
5423                     this.targetEndpointDefinitions[elid][type] = _def;
5424                     _makeElementDropHandler(elInfo, p, dropOptions, p.isSource === true, true);
5425                     // stash the definition on the drop
5426                     elInfo.el._katavorioDrop[elInfo.el._katavorioDrop.length - 1].targetDef = _def;
5427 
5428                 }.bind(this);
5429 
5430             // make an array if only given one element
5431             var inputs = el.length && el.constructor !== String ? el : [ el ];
5432 
5433             // register each one in the list.
5434             for (var i = 0, ii = inputs.length; i < ii; i++) {
5435                 _doOne(inputs[i]);
5436             }
5437 
5438             return this;
5439         };
5440 
5441         // see api docs
5442         this.unmakeTarget = function (el, doNotClearArrays) {
5443             var info = _info(el);
5444             _currentInstance.destroyDroppable(info.el, "internal");
5445             if (!doNotClearArrays) {
5446                 delete this.targetEndpointDefinitions[info.id];
5447             }
5448 
5449             return this;
5450         };
5451 
5452         // see api docs
5453         this.makeSource = function (el, params, referenceParams) {
5454             var p = root.jsPlumb.extend({_jsPlumb: this}, referenceParams);
5455             root.jsPlumb.extend(p, params);
5456             var type = p.connectionType || "default";
5457             var aae = _currentInstance.deriveEndpointAndAnchorSpec(type);
5458             p.endpoint = p.endpoint || aae.endpoints[0];
5459             p.anchor = p.anchor || aae.anchors[0];
5460             var maxConnections = p.maxConnections || -1,
5461                 onMaxConnections = p.onMaxConnections,
5462                 _doOne = function (elInfo) {
5463                     // get the element's id and store the endpoint definition for it.  jsPlumb.connect calls will look for one of these,
5464                     // and use the endpoint definition if found.
5465                     var elid = elInfo.id,
5466                         _del = this.getElement(elInfo.el);
5467 
5468                     this.sourceEndpointDefinitions[elid] = this.sourceEndpointDefinitions[elid] || {};
5469                     _ensureContainer(elid);
5470 
5471                     var _def = {
5472                         def:root.jsPlumb.extend({}, p),
5473                         uniqueEndpoint: p.uniqueEndpoint,
5474                         maxConnections: maxConnections,
5475                         enabled: true
5476                     };
5477 
5478 
5479                     this.sourceEndpointDefinitions[elid][type] = _def;
5480                     elInfo.def = _def;
5481 
5482                     var stopEvent = root.jsPlumb.dragEvents.stop,
5483                         dragEvent = root.jsPlumb.dragEvents.drag,
5484                         dragOptions = root.jsPlumb.extend({ }, p.dragOptions || {}),
5485                         existingDrag = dragOptions.drag,
5486                         existingStop = dragOptions.stop,
5487                         ep = null,
5488                         endpointAddedButNoDragYet = false;
5489 
5490                     // set scope if its not set in dragOptions but was passed in in params
5491                     dragOptions.scope = dragOptions.scope || p.scope;
5492 
5493                     dragOptions[dragEvent] = _ju.wrap(dragOptions[dragEvent], function () {
5494                         if (existingDrag) {
5495                             existingDrag.apply(this, arguments);
5496                         }
5497                         endpointAddedButNoDragYet = false;
5498                     });
5499 
5500                     dragOptions[stopEvent] = _ju.wrap(dragOptions[stopEvent], function () {
5501 
5502                         if (existingStop) {
5503                             existingStop.apply(this, arguments);
5504                         }
5505                         this.currentlyDragging = false;
5506                         if (ep._jsPlumb != null) { // if not cleaned up...
5507 
5508                             // reset the anchor to the anchor that was initially provided. the one we were using to drag
5509                             // the connection was just a placeholder that was located at the place the user pressed the
5510                             // mouse button to initiate the drag.
5511                             var anchorDef = p.anchor || this.Defaults.Anchor,
5512                                 oldAnchor = ep.anchor,
5513                                 oldConnection = ep.connections[0];
5514 
5515                             var    newAnchor = this.makeAnchor(anchorDef, elid, this),
5516                                 _el = ep.element;
5517 
5518                             // if the anchor has a 'positionFinder' set, then delegate to that function to find
5519                             // out where to locate the anchor. issue 117.
5520                             if (newAnchor.positionFinder != null) {
5521                                 var elPosition = _currentInstance.getOffset(_el),
5522                                     elSize = this.getSize(_el),
5523                                     dropPosition = { left: elPosition.left + (oldAnchor.x * elSize[0]), top: elPosition.top + (oldAnchor.y * elSize[1]) },
5524                                     ap = newAnchor.positionFinder(dropPosition, elPosition, elSize, newAnchor.constructorParams);
5525 
5526                                 newAnchor.x = ap[0];
5527                                 newAnchor.y = ap[1];
5528                             }
5529 
5530                             ep.setAnchor(newAnchor, true);
5531                             ep.repaint();
5532                             this.repaint(ep.elementId);
5533                             if (oldConnection != null) {
5534                                 this.repaint(oldConnection.targetId);
5535                             }
5536                         }
5537                     }.bind(this));
5538 
5539                     // when the user presses the mouse, add an Endpoint, if we are enabled.
5540                     var mouseDownListener = function (e) {
5541                         // on right mouse button, abort.
5542                         if (e.which === 3 || e.button === 2) {
5543                             return;
5544                         }
5545 
5546                         // TODO store def on element.
5547                         var def = this.sourceEndpointDefinitions[elid][type];
5548 
5549                         // if disabled, return.
5550                         if (!def.enabled) {
5551                             return;
5552                         }
5553 
5554                         elid = this.getId(this.getElement(elInfo.el)); // elid might have changed since this method was called to configure the element.
5555 
5556                         // if a filter was given, run it, and return if it says no.
5557                         if (p.filter) {
5558                             var r = _ju.isString(p.filter) ? selectorFilter(e, elInfo.el, p.filter, this, p.filterExclude) : p.filter(e, elInfo.el);
5559                             if (r === false) {
5560                                 return;
5561                             }
5562                         }
5563 
5564                         // if maxConnections reached
5565                         var sourceCount = this.select({source: elid}).length;
5566                         if (def.maxConnections >= 0 && (sourceCount >= def.maxConnections)) {
5567                             if (onMaxConnections) {
5568                                 onMaxConnections({
5569                                     element: elInfo.el,
5570                                     maxConnections: maxConnections
5571                                 }, e);
5572                             }
5573                             return false;
5574                         }
5575 
5576                         // find the position on the element at which the mouse was pressed; this is where the endpoint
5577                         // will be located.
5578                         var elxy = root.jsPlumb.getPositionOnElement(e, _del, _zoom);
5579 
5580                         // we need to override the anchor in here, and force 'isSource', but we don't want to mess with
5581                         // the params passed in, because after a connection is established we're going to reset the endpoint
5582                         // to have the anchor we were given.
5583                         var tempEndpointParams = {};
5584                         root.jsPlumb.extend(tempEndpointParams, p);
5585                         tempEndpointParams.isTemporarySource = true;
5586                         tempEndpointParams.anchor = [ elxy[0], elxy[1] , 0, 0];
5587                         tempEndpointParams.dragOptions = dragOptions;
5588 
5589                         if (def.def.scope) {
5590                             tempEndpointParams.scope = def.def.scope;
5591                         }
5592 
5593                         ep = this.addEndpoint(elid, tempEndpointParams);
5594                         endpointAddedButNoDragYet = true;
5595                         ep.setDeleteOnEmpty(true);
5596 
5597                         // if unique endpoint and it's already been created, push it onto the endpoint we create. at the end
5598                         // of a successful connection we'll switch to that endpoint.
5599                         // TODO this is the same code as the programmatic endpoints create on line 1050 ish
5600                         if (def.uniqueEndpoint) {
5601                             if (!def.endpoint) {
5602                                 def.endpoint = ep;
5603                                 ep.setDeleteOnEmpty(false);
5604                             }
5605                             else {
5606                                 ep.finalEndpoint = def.endpoint;
5607                             }
5608                         }
5609 
5610                         var _delTempEndpoint = function () {
5611                             // this mouseup event is fired only if no dragging occurred, by jquery and yui, but for mootools
5612                             // it is fired even if dragging has occurred, in which case we would blow away a perfectly
5613                             // legitimate endpoint, were it not for this check.  the flag is set after adding an
5614                             // endpoint and cleared in a drag listener we set in the dragOptions above.
5615                             _currentInstance.off(ep.canvas, "mouseup", _delTempEndpoint);
5616                             _currentInstance.off(elInfo.el, "mouseup", _delTempEndpoint);
5617                             if (endpointAddedButNoDragYet) {
5618                                 endpointAddedButNoDragYet = false;
5619                                 _currentInstance.deleteEndpoint(ep);
5620                             }
5621                         };
5622 
5623                         _currentInstance.on(ep.canvas, "mouseup", _delTempEndpoint);
5624                         _currentInstance.on(elInfo.el, "mouseup", _delTempEndpoint);
5625 
5626                         // optionally check for attributes to extract from the source element
5627                         var payload = {};
5628                         if (def.def.extract) {
5629                             for (var att in def.def.extract) {
5630                                 var v = (e.srcElement || e.target).getAttribute(att);
5631                                 if (v) {
5632                                     payload[def.def.extract[att]] = v;
5633                                 }
5634                             }
5635                         }
5636 
5637                         // and then trigger its mousedown event, which will kick off a drag, which will start dragging
5638                         // a new connection from this endpoint.
5639                         _currentInstance.trigger(ep.canvas, "mousedown", e, payload);
5640 
5641                         _ju.consume(e);
5642 
5643                     }.bind(this);
5644 
5645                     this.on(elInfo.el, "mousedown", mouseDownListener);
5646                     _def.trigger = mouseDownListener;
5647 
5648                     // if a filter was provided, set it as a dragFilter on the element,
5649                     // to prevent the element drag function from kicking in when we want to
5650                     // drag a new connection
5651                     if (p.filter && (_ju.isString(p.filter) || _ju.isFunction(p.filter))) {
5652                         _currentInstance.setDragFilter(elInfo.el, p.filter);
5653                     }
5654 
5655                     var dropOptions = root.jsPlumb.extend({}, p.dropOptions || {});
5656 
5657                     _makeElementDropHandler(elInfo, p, dropOptions, true, p.isTarget === true);
5658 
5659                 }.bind(this);
5660 
5661             var inputs = el.length && el.constructor !== String ? el : [ el ];
5662             for (var i = 0, ii = inputs.length; i < ii; i++) {
5663                 _doOne(_info(inputs[i]));
5664             }
5665 
5666             return this;
5667         };
5668 
5669         // see api docs
5670         this.unmakeSource = function (el, connectionType, doNotClearArrays) {
5671             var info = _info(el);
5672             _currentInstance.destroyDroppable(info.el, "internal");
5673             var eldefs = this.sourceEndpointDefinitions[info.id];
5674             if (eldefs) {
5675                 for (var def in eldefs) {
5676                     if (connectionType == null || connectionType === def) {
5677                         var mouseDownListener = eldefs[def].trigger;
5678                         if (mouseDownListener) {
5679                             _currentInstance.off(info.el, "mousedown", mouseDownListener);
5680                         }
5681                         if (!doNotClearArrays) {
5682                             delete this.sourceEndpointDefinitions[info.id][def];
5683                         }
5684                     }
5685                 }
5686             }
5687 
5688             return this;
5689         };
5690 
5691         // see api docs
5692         this.unmakeEverySource = function () {
5693             for (var i in this.sourceEndpointDefinitions) {
5694                 _currentInstance.unmakeSource(i, null, true);
5695             }
5696 
5697             this.sourceEndpointDefinitions = {};
5698             return this;
5699         };
5700 
5701         var _getScope = function (el, types, connectionType) {
5702             types = _ju.isArray(types) ? types : [ types ];
5703             var id = _getId(el);
5704             connectionType = connectionType || "default";
5705             for (var i = 0; i < types.length; i++) {
5706                 var eldefs = this[types[i]][id];
5707                 if (eldefs && eldefs[connectionType]) {
5708                     return eldefs[connectionType].def.scope || this.Defaults.Scope;
5709                 }
5710             }
5711         }.bind(this);
5712 
5713         var _setScope = function (el, scope, types, connectionType) {
5714             types = _ju.isArray(types) ? types : [ types ];
5715             var id = _getId(el);
5716             connectionType = connectionType || "default";
5717             for (var i = 0; i < types.length; i++) {
5718                 var eldefs = this[types[i]][id];
5719                 if (eldefs && eldefs[connectionType]) {
5720                     eldefs[connectionType].def.scope = scope;
5721                 }
5722             }
5723 
5724         }.bind(this);
5725 
5726         this.getScope = function (el, scope) {
5727             return _getScope(el, [ "sourceEndpointDefinitions", "targetEndpointDefinitions" ]);
5728         };
5729         this.getSourceScope = function (el) {
5730             return _getScope(el, "sourceEndpointDefinitions");
5731         };
5732         this.getTargetScope = function (el) {
5733             return _getScope(el, "targetEndpointDefinitions");
5734         };
5735         this.setScope = function (el, scope, connectionType) {
5736             this.setSourceScope(el, scope, connectionType);
5737             this.setTargetScope(el, scope, connectionType);
5738         };
5739         this.setSourceScope = function (el, scope, connectionType) {
5740             _setScope(el, scope, "sourceEndpointDefinitions", connectionType);
5741             // we get the source scope during the mousedown event, but we also want to set this.
5742             this.setDragScope(el, scope);
5743         };
5744         this.setTargetScope = function (el, scope, connectionType) {
5745             _setScope(el, scope, "targetEndpointDefinitions", connectionType);
5746             this.setDropScope(el, scope);
5747         };
5748 
5749         // see api docs
5750         this.unmakeEveryTarget = function () {
5751             for (var i in this.targetEndpointDefinitions) {
5752                 _currentInstance.unmakeTarget(i, true);
5753             }
5754 
5755             this.targetEndpointDefinitions = {};
5756             return this;
5757         };
5758 
5759         // does the work of setting a source enabled or disabled.
5760         var _setEnabled = function (type, el, state, toggle, connectionType) {
5761             var a = type === "source" ? this.sourceEndpointDefinitions : this.targetEndpointDefinitions,
5762                 originalState, info, newState;
5763 
5764             connectionType = connectionType || "default";
5765 
5766             // a selector or an array
5767             if (el.length && !_ju.isString(el)) {
5768                 originalState = [];
5769                 for (var i = 0, ii = el.length; i < ii; i++) {
5770                     info = _info(el[i]);
5771                     if (a[info.id] && a[info.id][connectionType]) {
5772                         originalState[i] = a[info.id][connectionType].enabled;
5773                         newState = toggle ? !originalState[i] : state;
5774                         a[info.id][connectionType].enabled = newState;
5775                         _currentInstance[newState ? "removeClass" : "addClass"](info.el, "jtk-" + type + "-disabled");
5776                     }
5777                 }
5778             }
5779             // otherwise a DOM element or a String ID.
5780             else {
5781                 info = _info(el);
5782                 var id = info.id;
5783                 if (a[id] && a[id][connectionType]) {
5784                     originalState = a[id][connectionType].enabled;
5785                     newState = toggle ? !originalState : state;
5786                     a[id][connectionType].enabled = newState;
5787                     _currentInstance[newState ? "removeClass" : "addClass"](info.el, "jtk-" + type + "-disabled");
5788                 }
5789             }
5790             return originalState;
5791         }.bind(this);
5792 
5793         var _first = function (el, fn) {
5794             if (_ju.isString(el) || !el.length) {
5795                 return fn.apply(this, [ el ]);
5796             }
5797             else if (el.length) {
5798                 return fn.apply(this, [ el[0] ]);
5799             }
5800 
5801         }.bind(this);
5802 
5803         this.toggleSourceEnabled = function (el, connectionType) {
5804             _setEnabled("source", el, null, true, connectionType);
5805             return this.isSourceEnabled(el, connectionType);
5806         };
5807 
5808         this.setSourceEnabled = function (el, state, connectionType) {
5809             return _setEnabled("source", el, state, null, connectionType);
5810         };
5811         this.isSource = function (el, connectionType) {
5812             connectionType = connectionType || "default";
5813             return _first(el, function (_el) {
5814                 var eldefs = this.sourceEndpointDefinitions[_info(_el).id];
5815                 return eldefs != null && eldefs[connectionType] != null;
5816             }.bind(this));
5817         };
5818         this.isSourceEnabled = function (el, connectionType) {
5819             connectionType = connectionType || "default";
5820             return _first(el, function (_el) {
5821                 var sep = this.sourceEndpointDefinitions[_info(_el).id];
5822                 return sep && sep[connectionType] && sep[connectionType].enabled === true;
5823             }.bind(this));
5824         };
5825 
5826         this.toggleTargetEnabled = function (el, connectionType) {
5827             _setEnabled("target", el, null, true, connectionType);
5828             return this.isTargetEnabled(el, connectionType);
5829         };
5830 
5831         this.isTarget = function (el, connectionType) {
5832             connectionType = connectionType || "default";
5833             return _first(el, function (_el) {
5834                 var eldefs = this.targetEndpointDefinitions[_info(_el).id];
5835                 return eldefs != null && eldefs[connectionType] != null;
5836             }.bind(this));
5837         };
5838         this.isTargetEnabled = function (el, connectionType) {
5839             connectionType = connectionType || "default";
5840             return _first(el, function (_el) {
5841                 var tep = this.targetEndpointDefinitions[_info(_el).id];
5842                 return tep && tep[connectionType] && tep[connectionType].enabled === true;
5843             }.bind(this));
5844         };
5845         this.setTargetEnabled = function (el, state, connectionType) {
5846             return _setEnabled("target", el, state, null, connectionType);
5847         };
5848 
5849 // --------------------- end makeSource/makeTarget ----------------------------------------------
5850 
5851         this.ready = function (fn) {
5852             _currentInstance.bind("ready", fn);
5853         };
5854 
5855         var _elEach = function(el, fn) {
5856             // support both lists...
5857             if (typeof el === 'object' && el.length) {
5858                 for (var i = 0, ii = el.length; i < ii; i++) {
5859                     fn(el[i]);
5860                 }
5861             }
5862             else {// ...and single strings or elements.
5863                 fn(el);
5864             }
5865 
5866             return _currentInstance;
5867         };
5868 
5869         // repaint some element's endpoints and connections
5870         this.repaint = function (el, ui, timestamp) {
5871             return _elEach(el, function(_el) {
5872                 _draw(_el, ui, timestamp);
5873             });
5874         };
5875 
5876         this.revalidate = function (el, timestamp, isIdAlready) {
5877             return _elEach(el, function(_el) {
5878                 var elId = isIdAlready ? _el : _currentInstance.getId(_el);
5879                 _currentInstance.updateOffset({ elId: elId, recalc: true, timestamp:timestamp });
5880                 var dm = _currentInstance.getDragManager();
5881                 if (dm) {
5882                     dm.updateOffsets(elId);
5883                 }
5884                 _currentInstance.repaint(_el);
5885             });
5886         };
5887 
5888         // repaint every endpoint and connection.
5889         this.repaintEverything = function () {
5890             // TODO this timestamp causes continuous anchors to not repaint properly.
5891             // fix this. do not just take out the timestamp. it runs a lot faster with
5892             // the timestamp included.
5893             var timestamp = _timestamp(), elId;
5894 
5895             for (elId in endpointsByElement) {
5896                 _currentInstance.updateOffset({ elId: elId, recalc: true, timestamp: timestamp });
5897             }
5898 
5899             for (elId in endpointsByElement) {
5900                 _draw(elId, null, timestamp);
5901             }
5902 
5903             return this;
5904         };
5905 
5906         this.removeAllEndpoints = function (el, recurse, affectedElements) {
5907             affectedElements = affectedElements || [];
5908             var _one = function (_el) {
5909                 var info = _info(_el),
5910                     ebe = endpointsByElement[info.id],
5911                     i, ii;
5912 
5913                 if (ebe) {
5914                     affectedElements.push(info);
5915                     for (i = 0, ii = ebe.length; i < ii; i++) {
5916                         _currentInstance.deleteEndpoint(ebe[i], false);
5917                     }
5918                 }
5919                 delete endpointsByElement[info.id];
5920 
5921                 if (recurse) {
5922                     if (info.el && info.el.nodeType !== 3 && info.el.nodeType !== 8) {
5923                         for (i = 0, ii = info.el.childNodes.length; i < ii; i++) {
5924                             _one(info.el.childNodes[i]);
5925                         }
5926                     }
5927                 }
5928 
5929             };
5930             _one(el);
5931             return this;
5932         };
5933 
5934         var _doRemove = function(info, affectedElements) {
5935             _currentInstance.removeAllEndpoints(info.id, true, affectedElements);
5936             var dm = _currentInstance.getDragManager();
5937             var _one = function(_info) {
5938 
5939                 if (dm) {
5940                     dm.elementRemoved(_info.id);
5941                 }
5942                 _currentInstance.anchorManager.clearFor(_info.id);
5943                 _currentInstance.anchorManager.removeFloatingConnection(_info.id);
5944 
5945                 if (_currentInstance.isSource(_info.el)) {
5946                     _currentInstance.unmakeSource(_info.el);
5947                 }
5948                 if (_currentInstance.isTarget(_info.el)) {
5949                     _currentInstance.unmakeTarget(_info.el);
5950                 }
5951                 _currentInstance.destroyDraggable(_info.el);
5952                 _currentInstance.destroyDroppable(_info.el);
5953 
5954 
5955                 delete _currentInstance.floatingConnections[_info.id];
5956                 delete managedElements[_info.id];
5957                 delete offsets[_info.id];
5958                 if (_info.el) {
5959                     _currentInstance.removeElement(_info.el);
5960                     _info.el._jsPlumb = null;
5961                 }
5962             };
5963 
5964             // remove all affected child elements
5965             for (var ae = 1; ae < affectedElements.length; ae++) {
5966                 _one(affectedElements[ae]);
5967             }
5968             // and always remove the requested one from the dom.
5969             _one(info);
5970         };
5971 
5972         /**
5973          * Remove the given element, including cleaning up all endpoints registered for it.
5974          * This is exposed in the public API but also used internally by jsPlumb when removing the
5975          * element associated with a connection drag.
5976          */
5977         this.remove = function (el, doNotRepaint) {
5978             var info = _info(el), affectedElements = [];
5979             if (info.text) {
5980                 info.el.parentNode.removeChild(info.el);
5981             }
5982             else if (info.id) {
5983                 _currentInstance.batch(function () {
5984                     _doRemove(info, affectedElements);
5985                 }, doNotRepaint === false);
5986             }
5987             return _currentInstance;
5988         };
5989 
5990         this.empty = function (el, doNotRepaint) {
5991             var affectedElements = [];
5992             var _one = function(el, dontRemoveFocus) {
5993                 var info = _info(el);
5994                 if (info.text) {
5995                     info.el.parentNode.removeChild(info.el);
5996                 }
5997                 else if (info.el) {
5998                     while(info.el.childNodes.length > 0) {
5999                         _one(info.el.childNodes[0]);
6000                     }
6001                     if (!dontRemoveFocus) {
6002                         _doRemove(info, affectedElements);
6003                     }
6004                 }
6005             };
6006 
6007             _currentInstance.batch(function() {
6008                 _one(el, true);
6009             }, doNotRepaint === false);
6010 
6011             return _currentInstance;
6012         };
6013 
6014         this.reset = function () {
6015             _currentInstance.silently(function() {
6016                 _hoverSuspended = false;
6017                 _currentInstance.removeAllGroups();
6018                 _currentInstance.removeGroupManager();
6019                 _currentInstance.deleteEveryEndpoint();
6020                 _currentInstance.unbind();
6021                 this.targetEndpointDefinitions = {};
6022                 this.sourceEndpointDefinitions = {};
6023                 connections.length = 0;
6024                 if (this.doReset) {
6025                     this.doReset();
6026                 }
6027             }.bind(this));
6028         };
6029 
6030         var _clearObject = function (obj) {
6031             if (obj.canvas && obj.canvas.parentNode) {
6032                 obj.canvas.parentNode.removeChild(obj.canvas);
6033             }
6034             obj.cleanup();
6035             obj.destroy();
6036         };
6037 
6038         this.clear = function () {
6039             _currentInstance.select().each(_clearObject);
6040             _currentInstance.selectEndpoints().each(_clearObject);
6041 
6042             endpointsByElement = {};
6043             endpointsByUUID = {};
6044         };
6045 
6046         this.setDefaultScope = function (scope) {
6047             DEFAULT_SCOPE = scope;
6048             return _currentInstance;
6049         };
6050 
6051         // sets whether or not some element should be currently draggable.
6052         this.setDraggable = _setDraggable;
6053 
6054         this.deriveEndpointAndAnchorSpec = function(type, dontPrependDefault) {
6055             var bits = ((dontPrependDefault ? "" : "default ") + type).split(/[\s]/), eps = null, ep = null, a = null, as = null;
6056             for (var i = 0; i < bits.length; i++) {
6057                 var _t = _currentInstance.getType(bits[i], "connection");
6058                 if (_t) {
6059                     if (_t.endpoints) {
6060                         eps = _t.endpoints;
6061                     }
6062                     if (_t.endpoint) {
6063                         ep = _t.endpoint;
6064                     }
6065                     if (_t.anchors) {
6066                         as = _t.anchors;
6067                     }
6068                     if (_t.anchor) {
6069                         a = _t.anchor;
6070                     }
6071                 }
6072             }
6073             return { endpoints: eps ? eps : [ ep, ep ], anchors: as ? as : [a, a ]};
6074         };
6075 
6076         // sets the id of some element, changing whatever we need to to keep track.
6077         this.setId = function (el, newId, doNotSetAttribute) {
6078             //
6079             var id;
6080 
6081             if (_ju.isString(el)) {
6082                 id = el;
6083             }
6084             else {
6085                 el = this.getElement(el);
6086                 id = this.getId(el);
6087             }
6088 
6089             var sConns = this.getConnections({source: id, scope: '*'}, true),
6090                 tConns = this.getConnections({target: id, scope: '*'}, true);
6091 
6092             newId = "" + newId;
6093 
6094             if (!doNotSetAttribute) {
6095                 el = this.getElement(id);
6096                 this.setAttribute(el, "id", newId);
6097             }
6098             else {
6099                 el = this.getElement(newId);
6100             }
6101 
6102             endpointsByElement[newId] = endpointsByElement[id] || [];
6103             for (var i = 0, ii = endpointsByElement[newId].length; i < ii; i++) {
6104                 endpointsByElement[newId][i].setElementId(newId);
6105                 endpointsByElement[newId][i].setReferenceElement(el);
6106             }
6107             delete endpointsByElement[id];
6108 
6109             this.sourceEndpointDefinitions[newId] = this.sourceEndpointDefinitions[id];
6110             delete this.sourceEndpointDefinitions[id];
6111             this.targetEndpointDefinitions[newId] = this.targetEndpointDefinitions[id];
6112             delete this.targetEndpointDefinitions[id];
6113 
6114             this.anchorManager.changeId(id, newId);
6115             var dm = this.getDragManager();
6116             if (dm) {
6117                 dm.changeId(id, newId);
6118             }
6119             managedElements[newId] = managedElements[id];
6120             delete managedElements[id];
6121 
6122             var _conns = function (list, epIdx, type) {
6123                 for (var i = 0, ii = list.length; i < ii; i++) {
6124                     list[i].endpoints[epIdx].setElementId(newId);
6125                     list[i].endpoints[epIdx].setReferenceElement(el);
6126                     list[i][type + "Id"] = newId;
6127                     list[i][type] = el;
6128                 }
6129             };
6130             _conns(sConns, 0, "source");
6131             _conns(tConns, 1, "target");
6132 
6133             this.repaint(newId);
6134         };
6135 
6136         this.setDebugLog = function (debugLog) {
6137             log = debugLog;
6138         };
6139 
6140         this.setSuspendDrawing = function (val, repaintAfterwards) {
6141             var curVal = _suspendDrawing;
6142             _suspendDrawing = val;
6143             if (val) {
6144                 _suspendedAt = new Date().getTime();
6145             } else {
6146                 _suspendedAt = null;
6147             }
6148             if (repaintAfterwards) {
6149                 this.repaintEverything();
6150             }
6151             return curVal;
6152         };
6153 
6154         // returns whether or not drawing is currently suspended.
6155         this.isSuspendDrawing = function () {
6156             return _suspendDrawing;
6157         };
6158 
6159         // return timestamp for when drawing was suspended.
6160         this.getSuspendedAt = function () {
6161             return _suspendedAt;
6162         };
6163 
6164         this.batch = function (fn, doNotRepaintAfterwards) {
6165             var _wasSuspended = this.isSuspendDrawing();
6166             if (!_wasSuspended) {
6167                 this.setSuspendDrawing(true);
6168             }
6169             try {
6170                 fn();
6171             }
6172             catch (e) {
6173                 _ju.log("Function run while suspended failed", e);
6174             }
6175             if (!_wasSuspended) {
6176                 this.setSuspendDrawing(false, !doNotRepaintAfterwards);
6177             }
6178         };
6179 
6180         this.doWhileSuspended = this.batch;
6181 
6182         this.getCachedData = _getCachedData;
6183         this.timestamp = _timestamp;
6184         this.show = function (el, changeEndpoints) {
6185             _setVisible(el, "block", changeEndpoints);
6186             return _currentInstance;
6187         };
6188 
6189         // TODO: update this method to return the current state.
6190         this.toggleVisible = _toggleVisible;
6191         this.toggleDraggable = _toggleDraggable;
6192         this.addListener = this.bind;
6193     };
6194 
6195     _ju.extend(root.jsPlumbInstance, _ju.EventGenerator, {
6196         setAttribute: function (el, a, v) {
6197             this.setAttribute(el, a, v);
6198         },
6199         getAttribute: function (el, a) {
6200             return this.getAttribute(root.jsPlumb.getElement(el), a);
6201         },
6202         convertToFullOverlaySpec: function(spec) {
6203             if (_ju.isString(spec)) {
6204                 spec = [ spec, { } ];
6205             }
6206             spec[1].id = spec[1].id || _ju.uuid();
6207             return spec;
6208         },
6209         registerConnectionType: function (id, type) {
6210             this._connectionTypes[id] = root.jsPlumb.extend({}, type);
6211             if (type.overlays) {
6212                 var to = {};
6213                 for (var i = 0; i < type.overlays.length; i++) {
6214                     // if a string, convert to object representation so that we can store the typeid on it.
6215                     // also assign an id.
6216                     var fo = this.convertToFullOverlaySpec(type.overlays[i]);
6217                     to[fo[1].id] = fo;
6218                 }
6219                 this._connectionTypes[id].overlays = to;
6220             }
6221         },
6222         registerConnectionTypes: function (types) {
6223             for (var i in types) {
6224                 this.registerConnectionType(i, types[i]);
6225             }
6226         },
6227         registerEndpointType: function (id, type) {
6228             this._endpointTypes[id] = root.jsPlumb.extend({}, type);
6229             if (type.overlays) {
6230                 var to = {};
6231                 for (var i = 0; i < type.overlays.length; i++) {
6232                     // if a string, convert to object representation so that we can store the typeid on it.
6233                     // also assign an id.
6234                     var fo = this.convertToFullOverlaySpec(type.overlays[i]);
6235                     to[fo[1].id] = fo;
6236                 }
6237                 this._endpointTypes[id].overlays = to;
6238             }
6239         },
6240         registerEndpointTypes: function (types) {
6241             for (var i in types) {
6242                 this.registerEndpointType(i, types[i]);
6243             }
6244         },
6245         getType: function (id, typeDescriptor) {
6246             return typeDescriptor === "connection" ? this._connectionTypes[id] : this._endpointTypes[id];
6247         },
6248         setIdChanged: function (oldId, newId) {
6249             this.setId(oldId, newId, true);
6250         },
6251         // set parent: change the parent for some node and update all the registrations we need to.
6252         setParent: function (el, newParent) {
6253             var _dom = this.getElement(el),
6254                 _id = this.getId(_dom),
6255                 _pdom = this.getElement(newParent),
6256                 _pid = this.getId(_pdom),
6257                 dm = this.getDragManager();
6258 
6259             _dom.parentNode.removeChild(_dom);
6260             _pdom.appendChild(_dom);
6261             if (dm) {
6262                 dm.setParent(_dom, _id, _pdom, _pid);
6263             }
6264         },
6265         extend: function (o1, o2, names) {
6266             var i;
6267             if (names) {
6268                 for (i = 0; i < names.length; i++) {
6269                     o1[names[i]] = o2[names[i]];
6270                 }
6271             }
6272             else {
6273                 for (i in o2) {
6274                     o1[i] = o2[i];
6275                 }
6276             }
6277 
6278             return o1;
6279         },
6280         floatingConnections: {},
6281         getFloatingAnchorIndex: function (jpc) {
6282             return jpc.endpoints[0].isFloating() ? 0 : jpc.endpoints[1].isFloating() ? 1 : -1;
6283         }
6284     });
6285 
6286     jsPlumbInstance.prototype.Defaults = {
6287         Anchor: "Bottom",
6288         Anchors: [ null, null ],
6289         ConnectionsDetachable: true,
6290         ConnectionOverlays: [ ],
6291         Connector: "Bezier",
6292         Container: null,
6293         DoNotThrowErrors: false,
6294         DragOptions: { },
6295         DropOptions: { },
6296         Endpoint: "Dot",
6297         EndpointOverlays: [ ],
6298         Endpoints: [ null, null ],
6299         EndpointStyle: { fill: "#456" },
6300         EndpointStyles: [ null, null ],
6301         EndpointHoverStyle: null,
6302         EndpointHoverStyles: [ null, null ],
6303         HoverPaintStyle: null,
6304         LabelStyle: { color: "black" },
6305         LogEnabled: false,
6306         Overlays: [ ],
6307         MaxConnections: 1,
6308         PaintStyle: { "stroke-width": 4, stroke: "#456" },
6309         ReattachConnections: false,
6310         RenderMode: "svg",
6311         Scope: "jsPlumb_DefaultScope"
6312     };
6313 
6314 // --------------------- static instance + module registration -------------------------------------------
6315 
6316 // create static instance and assign to window if window exists.	
6317     var jsPlumb = new jsPlumbInstance();
6318     // register on 'root' (lets us run on server or browser)
6319     root.jsPlumb = jsPlumb;
6320     // add 'getInstance' method to static instance
6321     jsPlumb.getInstance = function (_defaults, overrideFns) {
6322         var j = new jsPlumbInstance(_defaults);
6323         if (overrideFns) {
6324             for (var ovf in overrideFns) {
6325                 j[ovf] = overrideFns[ovf];
6326             }
6327         }
6328         j.init();
6329         return j;
6330     };
6331     jsPlumb.each = function (spec, fn) {
6332         if (spec == null) {
6333             return;
6334         }
6335         if (typeof spec === "string") {
6336             fn(jsPlumb.getElement(spec));
6337         }
6338         else if (spec.length != null) {
6339             for (var i = 0; i < spec.length; i++) {
6340                 fn(jsPlumb.getElement(spec[i]));
6341             }
6342         }
6343         else {
6344             fn(spec);
6345         } // assume it's an element.
6346     };
6347 
6348     // CommonJS
6349     if (typeof exports !== 'undefined') {
6350         exports.jsPlumb = jsPlumb;
6351     }
6352 
6353 // --------------------- end static instance + AMD registration -------------------------------------------		
6354 
6355 }).call(typeof window !== 'undefined' ? window : this);
6356 
6357 /*
6358  * jsPlumb Community Edition
6359  *
6360  * Provides a way to visually connect elements on an HTML page, using SVG.
6361  *
6362  * This file contains the base functionality for DOM type adapters.
6363  *
6364  * Copyright (c) 2010 - 2017 jsPlumb (hello@jsplumbtoolkit.com)
6365  *
6366  * https://jsplumbtoolkit.com
6367  * https://github.com/jsplumb/jsplumb
6368  *
6369  * Dual licensed under the MIT and GPL2 licenses.
6370  */
6371 ;
6372 (function () {
6373 
6374     var root = this, _ju = root.jsPlumbUtil;
6375 
6376     var _genLoc = function (prefix, e) {
6377             if (e == null) {
6378                 return [ 0, 0 ];
6379             }
6380             var ts = _touches(e), t = _getTouch(ts, 0);
6381             return [t[prefix + "X"], t[prefix + "Y"]];
6382         },
6383         _pageLocation = _genLoc.bind(this, "page"),
6384         _screenLocation = _genLoc.bind(this, "screen"),
6385         _clientLocation = _genLoc.bind(this, "client"),
6386         _getTouch = function (touches, idx) {
6387             return touches.item ? touches.item(idx) : touches[idx];
6388         },
6389         _touches = function (e) {
6390             return e.touches && e.touches.length > 0 ? e.touches :
6391                     e.changedTouches && e.changedTouches.length > 0 ? e.changedTouches :
6392                     e.targetTouches && e.targetTouches.length > 0 ? e.targetTouches :
6393                 [ e ];
6394         };
6395 
6396     /**
6397      Manages dragging for some instance of jsPlumb.
6398 
6399      TODO instead of this being accessed directly, it should subscribe to events on the jsPlumb instance: every method
6400      in here is called directly by jsPlumb. But what should happen is that we have unpublished events that this listens
6401      to.  The only trick is getting one of these instantiated with every jsPlumb instance: it needs to have a hook somehow.
6402      Basically the general idea is to pull ALL the drag code out (prototype method registrations plus this) into a
6403      dedicated drag script), that does not necessarily need to be included.
6404 
6405 
6406      */
6407     var DragManager = function (_currentInstance) {
6408         var _draggables = {}, _dlist = [], _delements = {}, _elementsWithEndpoints = {},
6409         // elementids mapped to the draggable to which they belong.
6410             _draggablesForElements = {};
6411 
6412         /**
6413          register some element as draggable.  right now the drag init stuff is done elsewhere, and it is
6414          possible that will continue to be the case.
6415          */
6416         this.register = function (el) {
6417             var id = _currentInstance.getId(el),
6418                 parentOffset = _currentInstance.getOffset(el);
6419 
6420             if (!_draggables[id]) {
6421                 _draggables[id] = el;
6422                 _dlist.push(el);
6423                 _delements[id] = {};
6424             }
6425 
6426             // look for child elements that have endpoints and register them against this draggable.
6427             var _oneLevel = function (p) {
6428                 if (p) {
6429                     for (var i = 0; i < p.childNodes.length; i++) {
6430                         if (p.childNodes[i].nodeType !== 3 && p.childNodes[i].nodeType !== 8) {
6431                             var cEl = jsPlumb.getElement(p.childNodes[i]),
6432                                 cid = _currentInstance.getId(p.childNodes[i], null, true);
6433                             if (cid && _elementsWithEndpoints[cid] && _elementsWithEndpoints[cid] > 0) {
6434                                 var cOff = _currentInstance.getOffset(cEl);
6435                                 _delements[id][cid] = {
6436                                     id: cid,
6437                                     offset: {
6438                                         left: cOff.left - parentOffset.left,
6439                                         top: cOff.top - parentOffset.top
6440                                     }
6441                                 };
6442                                 _draggablesForElements[cid] = id;
6443                             }
6444                             _oneLevel(p.childNodes[i]);
6445                         }
6446                     }
6447                 }
6448             };
6449 
6450             _oneLevel(el);
6451         };
6452 
6453         // refresh the offsets for child elements of this element.
6454         this.updateOffsets = function (elId, childOffsetOverrides) {
6455             if (elId != null) {
6456                 childOffsetOverrides = childOffsetOverrides || {};
6457                 var domEl = jsPlumb.getElement(elId),
6458                     id = _currentInstance.getId(domEl),
6459                     children = _delements[id],
6460                     parentOffset = _currentInstance.getOffset(domEl);
6461 
6462                 if (children) {
6463                     for (var i in children) {
6464                         if (children.hasOwnProperty(i)) {
6465                             var cel = jsPlumb.getElement(i),
6466                                 cOff = childOffsetOverrides[i] || _currentInstance.getOffset(cel);
6467 
6468                             // do not update if we have a value already and we'd just be writing 0,0
6469                             if (cel.offsetParent == null && _delements[id][i] != null) {
6470                                 continue;
6471                             }
6472 
6473                             _delements[id][i] = {
6474                                 id: i,
6475                                 offset: {
6476                                     left: cOff.left - parentOffset.left,
6477                                     top: cOff.top - parentOffset.top
6478                                 }
6479                             };
6480                             _draggablesForElements[i] = id;
6481                         }
6482                     }
6483                 }
6484             }
6485         };
6486 
6487         /**
6488          notification that an endpoint was added to the given el.  we go up from that el's parent
6489          node, looking for a parent that has been registered as a draggable. if we find one, we add this
6490          el to that parent's list of elements to update on drag (if it is not there already)
6491          */
6492         this.endpointAdded = function (el, id) {
6493 
6494             id = id || _currentInstance.getId(el);
6495 
6496             var b = document.body,
6497                 p = el.parentNode;
6498 
6499             _elementsWithEndpoints[id] = _elementsWithEndpoints[id] ? _elementsWithEndpoints[id] + 1 : 1;
6500 
6501             while (p != null && p !== b) {
6502                 var pid = _currentInstance.getId(p, null, true);
6503                 if (pid && _draggables[pid]) {
6504                     var pLoc = _currentInstance.getOffset(p);
6505 
6506                     if (_delements[pid][id] == null) {
6507                         var cLoc = _currentInstance.getOffset(el);
6508                         _delements[pid][id] = {
6509                             id: id,
6510                             offset: {
6511                                 left: cLoc.left - pLoc.left,
6512                                 top: cLoc.top - pLoc.top
6513                             }
6514                         };
6515                         _draggablesForElements[id] = pid;
6516                     }
6517                     break;
6518                 }
6519                 p = p.parentNode;
6520             }
6521         };
6522 
6523         this.endpointDeleted = function (endpoint) {
6524             if (_elementsWithEndpoints[endpoint.elementId]) {
6525                 _elementsWithEndpoints[endpoint.elementId]--;
6526                 if (_elementsWithEndpoints[endpoint.elementId] <= 0) {
6527                     for (var i in _delements) {
6528                         if (_delements.hasOwnProperty(i) && _delements[i]) {
6529                             delete _delements[i][endpoint.elementId];
6530                             delete _draggablesForElements[endpoint.elementId];
6531                         }
6532                     }
6533                 }
6534             }
6535         };
6536 
6537         this.changeId = function (oldId, newId) {
6538             _delements[newId] = _delements[oldId];
6539             _delements[oldId] = {};
6540             _draggablesForElements[newId] = _draggablesForElements[oldId];
6541             _draggablesForElements[oldId] = null;
6542         };
6543 
6544         this.getElementsForDraggable = function (id) {
6545             return _delements[id];
6546         };
6547 
6548         this.elementRemoved = function (elementId) {
6549             var elId = _draggablesForElements[elementId];
6550             if (elId) {
6551                 delete _delements[elId][elementId];
6552                 delete _draggablesForElements[elementId];
6553             }
6554         };
6555 
6556         this.reset = function () {
6557             _draggables = {};
6558             _dlist = [];
6559             _delements = {};
6560             _elementsWithEndpoints = {};
6561         };
6562 
6563         //
6564         // notification drag ended. We check automatically if need to update some
6565         // ancestor's offsets.
6566         //
6567         this.dragEnded = function (el) {
6568             if (el.offsetParent != null) {
6569                 var id = _currentInstance.getId(el),
6570                     ancestor = _draggablesForElements[id];
6571 
6572                 if (ancestor) {
6573                     this.updateOffsets(ancestor);
6574                 }
6575             }
6576         };
6577 
6578         this.setParent = function (el, elId, p, pId, currentChildLocation) {
6579             var current = _draggablesForElements[elId];
6580             if (!_delements[pId]) {
6581                 _delements[pId] = {};
6582             }
6583             var pLoc = _currentInstance.getOffset(p),
6584                 cLoc = currentChildLocation || _currentInstance.getOffset(el);
6585 
6586             if (current && _delements[current]) {
6587                 delete _delements[current][elId];
6588             }
6589 
6590             _delements[pId][elId] = {
6591                 id:elId,
6592                 offset : {
6593                     left: cLoc.left - pLoc.left,
6594                     top: cLoc.top - pLoc.top
6595                 }
6596             };
6597             _draggablesForElements[elId] = pId;
6598         };
6599 
6600         this.clearParent = function(el, elId) {
6601             var current = _draggablesForElements[elId];
6602             if (current) {
6603                 delete _delements[current][elId];
6604                 delete _draggablesForElements[elId];
6605             }
6606         };
6607 
6608         this.revalidateParent = function(el, elId, childOffset) {
6609             var current = _draggablesForElements[elId];
6610             if (current) {
6611                 var co = {};
6612                 co[elId] = childOffset;
6613                 this.updateOffsets(current, co);
6614                 _currentInstance.revalidate(current);
6615             }
6616         };
6617 
6618         this.getDragAncestor = function (el) {
6619             var de = jsPlumb.getElement(el),
6620                 id = _currentInstance.getId(de),
6621                 aid = _draggablesForElements[id];
6622 
6623             if (aid) {
6624                 return jsPlumb.getElement(aid);
6625             }
6626             else {
6627                 return null;
6628             }
6629         };
6630 
6631     };
6632 
6633     var trim = function (str) {
6634             return str == null ? null : (str.replace(/^\s\s*/, '').replace(/\s\s*$/, ''));
6635         },
6636         _setClassName = function (el, cn) {
6637             cn = trim(cn);
6638             if (typeof el.className.baseVal !== "undefined") {
6639                 el.className.baseVal = cn;
6640             }
6641             else {
6642                 el.className = cn;
6643             }
6644         },
6645         _getClassName = function (el) {
6646             return (typeof el.className.baseVal === "undefined") ? el.className : el.className.baseVal;
6647         },
6648         _classManip = function (el, classesToAdd, classesToRemove) {
6649             classesToAdd = classesToAdd == null ? [] : _ju.isArray(classesToAdd) ? classesToAdd : classesToAdd.split(/\s+/);
6650             classesToRemove = classesToRemove == null ? [] : _ju.isArray(classesToRemove) ? classesToRemove : classesToRemove.split(/\s+/);
6651 
6652             var className = _getClassName(el),
6653                 curClasses = className.split(/\s+/);
6654 
6655             var _oneSet = function (add, classes) {
6656                 for (var i = 0; i < classes.length; i++) {
6657                     if (add) {
6658                         if (curClasses.indexOf(classes[i]) === -1) {
6659                             curClasses.push(classes[i]);
6660                         }
6661                     }
6662                     else {
6663                         var idx = curClasses.indexOf(classes[i]);
6664                         if (idx !== -1) {
6665                             curClasses.splice(idx, 1);
6666                         }
6667                     }
6668                 }
6669             };
6670 
6671             _oneSet(true, classesToAdd);
6672             _oneSet(false, classesToRemove);
6673 
6674             _setClassName(el, curClasses.join(" "));
6675         };
6676 
6677     root.jsPlumb.extend(root.jsPlumbInstance.prototype, {
6678 
6679         headless: false,
6680 
6681         pageLocation: _pageLocation,
6682         screenLocation: _screenLocation,
6683         clientLocation: _clientLocation,
6684 
6685         getDragManager:function() {
6686             if (this.dragManager == null) {
6687                 this.dragManager = new DragManager(this);
6688             }
6689 
6690             return this.dragManager;
6691         },
6692 
6693         recalculateOffsets:function(elId) {
6694             this.getDragManager().updateOffsets(elId);
6695         },
6696 
6697         createElement:function(tag, style, clazz, atts) {
6698             return this.createElementNS(null, tag, style, clazz, atts);
6699         },
6700 
6701         createElementNS:function(ns, tag, style, clazz, atts) {
6702             var e = ns == null ? document.createElement(tag) : document.createElementNS(ns, tag);
6703             var i;
6704             style = style || {};
6705             for (i in style) {
6706                 e.style[i] = style[i];
6707             }
6708 
6709             if (clazz) {
6710                 e.className = clazz;
6711             }
6712 
6713             atts = atts || {};
6714             for (i in atts) {
6715                 e.setAttribute(i, "" + atts[i]);
6716             }
6717 
6718             return e;
6719         },
6720 
6721         getAttribute: function (el, attName) {
6722             return el.getAttribute != null ? el.getAttribute(attName) : null;
6723         },
6724 
6725         setAttribute: function (el, a, v) {
6726             if (el.setAttribute != null) {
6727                 el.setAttribute(a, v);
6728             }
6729         },
6730 
6731         setAttributes: function (el, atts) {
6732             for (var i in atts) {
6733                 if (atts.hasOwnProperty(i)) {
6734                     el.setAttribute(i, atts[i]);
6735                 }
6736             }
6737         },
6738         appendToRoot: function (node) {
6739             document.body.appendChild(node);
6740         },
6741         getRenderModes: function () {
6742             return [ "svg"  ];
6743         },
6744         getClass:_getClassName,
6745         addClass: function (el, clazz) {
6746             jsPlumb.each(el, function (e) {
6747                 _classManip(e, clazz);
6748             });
6749         },
6750         hasClass: function (el, clazz) {
6751             el = jsPlumb.getElement(el);
6752             if (el.classList) {
6753                 return el.classList.contains(clazz);
6754             }
6755             else {
6756                 return _getClassName(el).indexOf(clazz) !== -1;
6757             }
6758         },
6759         removeClass: function (el, clazz) {
6760             jsPlumb.each(el, function (e) {
6761                 _classManip(e, null, clazz);
6762             });
6763         },
6764         updateClasses: function (el, toAdd, toRemove) {
6765             jsPlumb.each(el, function (e) {
6766                 _classManip(e, toAdd, toRemove);
6767             });
6768         },
6769         setClass: function (el, clazz) {
6770             jsPlumb.each(el, function (e) {
6771                 _setClassName(e, clazz);
6772             });
6773         },
6774         setPosition: function (el, p) {
6775             el.style.left = p.left + "px";
6776             el.style.top = p.top + "px";
6777         },
6778         getPosition: function (el) {
6779             var _one = function (prop) {
6780                 var v = el.style[prop];
6781                 return v ? v.substring(0, v.length - 2) : 0;
6782             };
6783             return {
6784                 left: _one("left"),
6785                 top: _one("top")
6786             };
6787         },
6788         getStyle:function(el, prop) {
6789             if (typeof window.getComputedStyle !== 'undefined') {
6790                 return getComputedStyle(el, null).getPropertyValue(prop);
6791             } else {
6792                 return el.currentStyle[prop];
6793             }
6794         },
6795         getSelector: function (ctx, spec) {
6796             var sel = null;
6797             if (arguments.length === 1) {
6798                 sel = ctx.nodeType != null ? ctx : document.querySelectorAll(ctx);
6799             }
6800             else {
6801                 sel = ctx.querySelectorAll(spec);
6802             }
6803 
6804             return sel;
6805         },
6806         getOffset:function(el, relativeToRoot, container) {
6807             el = jsPlumb.getElement(el);
6808             container = container || this.getContainer();
6809             var out = {
6810                     left: el.offsetLeft,
6811                     top: el.offsetTop
6812                 },
6813                 op = (relativeToRoot  || (container != null && (el !== container && el.offsetParent !== container))) ?  el.offsetParent : null,
6814                 _maybeAdjustScroll = function(offsetParent) {
6815                     if (offsetParent != null && offsetParent !== document.body && (offsetParent.scrollTop > 0 || offsetParent.scrollLeft > 0)) {
6816                         out.left -= offsetParent.scrollLeft;
6817                         out.top -= offsetParent.scrollTop;
6818                     }
6819                 }.bind(this);
6820 
6821             while (op != null) {
6822                 out.left += op.offsetLeft;
6823                 out.top += op.offsetTop;
6824                 _maybeAdjustScroll(op);
6825                 op = relativeToRoot ? op.offsetParent :
6826                         op.offsetParent === container ? null : op.offsetParent;
6827             }
6828 
6829             // if container is scrolled and the element (or its offset parent) is not absolute or fixed, adjust accordingly.
6830             if (container != null && !relativeToRoot && (container.scrollTop > 0 || container.scrollLeft > 0)) {
6831                 var pp = el.offsetParent != null ? this.getStyle(el.offsetParent, "position") : "static",
6832                     p = this.getStyle(el, "position");
6833                 if (p !== "absolute" && p !== "fixed" && pp !== "absolute" && pp !== "fixed") {
6834                     out.left -= container.scrollLeft;
6835                     out.top -= container.scrollTop;
6836                 }
6837             }
6838             return out;
6839         },
6840         //
6841         // return x+y proportion of the given element's size corresponding to the location of the given event.
6842         //
6843         getPositionOnElement: function (evt, el, zoom) {
6844             var box = typeof el.getBoundingClientRect !== "undefined" ? el.getBoundingClientRect() : { left: 0, top: 0, width: 0, height: 0 },
6845                 body = document.body,
6846                 docElem = document.documentElement,
6847                 scrollTop = window.pageYOffset || docElem.scrollTop || body.scrollTop,
6848                 scrollLeft = window.pageXOffset || docElem.scrollLeft || body.scrollLeft,
6849                 clientTop = docElem.clientTop || body.clientTop || 0,
6850                 clientLeft = docElem.clientLeft || body.clientLeft || 0,
6851                 pst = 0,
6852                 psl = 0,
6853                 top = box.top + scrollTop - clientTop + (pst * zoom),
6854                 left = box.left + scrollLeft - clientLeft + (psl * zoom),
6855                 cl = jsPlumb.pageLocation(evt),
6856                 w = box.width || (el.offsetWidth * zoom),
6857                 h = box.height || (el.offsetHeight * zoom),
6858                 x = (cl[0] - left) / w,
6859                 y = (cl[1] - top) / h;
6860 
6861             return [ x, y ];
6862         },
6863 
6864         /**
6865          * Gets the absolute position of some element as read from the left/top properties in its style.
6866          * @method getAbsolutePosition
6867          * @param {Element} el The element to retrieve the absolute coordinates from. **Note** this is a DOM element, not a selector from the underlying library.
6868          * @return {Number[]} [left, top] pixel values.
6869          */
6870         getAbsolutePosition: function (el) {
6871             var _one = function (s) {
6872                 var ss = el.style[s];
6873                 if (ss) {
6874                     return parseFloat(ss.substring(0, ss.length - 2));
6875                 }
6876             };
6877             return [ _one("left"), _one("top") ];
6878         },
6879 
6880         /**
6881          * Sets the absolute position of some element by setting the left/top properties in its style.
6882          * @method setAbsolutePosition
6883          * @param {Element} el The element to set the absolute coordinates on. **Note** this is a DOM element, not a selector from the underlying library.
6884          * @param {Number[]} xy x and y coordinates
6885          * @param {Number[]} [animateFrom] Optional previous xy to animate from.
6886          * @param {Object} [animateOptions] Options for the animation.
6887          */
6888         setAbsolutePosition: function (el, xy, animateFrom, animateOptions) {
6889             if (animateFrom) {
6890                 this.animate(el, {
6891                     left: "+=" + (xy[0] - animateFrom[0]),
6892                     top: "+=" + (xy[1] - animateFrom[1])
6893                 }, animateOptions);
6894             }
6895             else {
6896                 el.style.left = xy[0] + "px";
6897                 el.style.top = xy[1] + "px";
6898             }
6899         },
6900         /**
6901          * gets the size for the element, in an array : [ width, height ].
6902          */
6903         getSize: function (el) {
6904             return [ el.offsetWidth, el.offsetHeight ];
6905         },
6906         getWidth: function (el) {
6907             return el.offsetWidth;
6908         },
6909         getHeight: function (el) {
6910             return el.offsetHeight;
6911         },
6912         getRenderMode : function() { return "svg"; }
6913 
6914     });
6915 }).call(typeof window !== 'undefined' ? window : this);
6916 
6917 /*
6918  * jsPlumb Community Edition
6919  *
6920  * Provides a way to visually connect elements on an HTML page, using SVG.
6921  *
6922  * This file contains code for components that support overlays.
6923  *
6924  * Copyright (c) 2010 - 2017 jsPlumb (hello@jsplumbtoolkit.com)
6925  *
6926  * https://jsplumbtoolkit.com
6927  * https://github.com/jsplumb/jsplumb
6928  *
6929  * Dual licensed under the MIT and GPL2 licenses.
6930  */
6931 ;(function() {
6932 
6933     "use strict";
6934     var root = this, _jp = root.jsPlumb, _ju = root.jsPlumbUtil;
6935 
6936     // ------------------------------ BEGIN OverlayCapablejsPlumbUIComponent --------------------------------------------
6937 
6938     var _internalLabelOverlayId = "__label",
6939     // this is a shortcut helper method to let people add a label as
6940     // overlay.
6941         _makeLabelOverlay = function (component, params) {
6942 
6943             var _params = {
6944                     cssClass: params.cssClass,
6945                     labelStyle: component.labelStyle,
6946                     id: _internalLabelOverlayId,
6947                     component: component,
6948                     _jsPlumb: component._jsPlumb.instance  // TODO not necessary, since the instance can be accessed through the component.
6949                 },
6950                 mergedParams = _jp.extend(_params, params);
6951 
6952             return new _jp.Overlays[component._jsPlumb.instance.getRenderMode()].Label(mergedParams);
6953         },
6954         _processOverlay = function (component, o) {
6955             var _newOverlay = null;
6956             if (_ju.isArray(o)) {	// this is for the shorthand ["Arrow", { width:50 }] syntax
6957                 // there's also a three arg version:
6958                 // ["Arrow", { width:50 }, {location:0.7}]
6959                 // which merges the 3rd arg into the 2nd.
6960                 var type = o[0],
6961                 // make a copy of the object so as not to mess up anyone else's reference...
6962                     p = _jp.extend({component: component, _jsPlumb: component._jsPlumb.instance}, o[1]);
6963                 if (o.length === 3) {
6964                     _jp.extend(p, o[2]);
6965                 }
6966                 _newOverlay = new _jp.Overlays[component._jsPlumb.instance.getRenderMode()][type](p);
6967             } else if (o.constructor === String) {
6968                 _newOverlay = new _jp.Overlays[component._jsPlumb.instance.getRenderMode()][o]({component: component, _jsPlumb: component._jsPlumb.instance});
6969             } else {
6970                 _newOverlay = o;
6971             }
6972 
6973             _newOverlay.id = _newOverlay.id || _ju.uuid();
6974             component.cacheTypeItem("overlay", _newOverlay, _newOverlay.id);
6975             component._jsPlumb.overlays[_newOverlay.id] = _newOverlay;
6976 
6977             return _newOverlay;
6978         };
6979 
6980     _jp.OverlayCapableJsPlumbUIComponent = function (params) {
6981 
6982         root.jsPlumbUIComponent.apply(this, arguments);
6983         this._jsPlumb.overlays = {};
6984         this._jsPlumb.overlayPositions = {};
6985 
6986         if (params.label) {
6987             this.getDefaultType().overlays[_internalLabelOverlayId] = ["Label", {
6988                 label: params.label,
6989                 location: params.labelLocation || this.defaultLabelLocation || 0.5,
6990                 labelStyle: params.labelStyle || this._jsPlumb.instance.Defaults.LabelStyle,
6991                 id:_internalLabelOverlayId
6992             }];
6993         }
6994 
6995         this.setListenerComponent = function (c) {
6996             if (this._jsPlumb) {
6997                 for (var i in this._jsPlumb.overlays) {
6998                     this._jsPlumb.overlays[i].setListenerComponent(c);
6999                 }
7000             }
7001         };
7002     };
7003 
7004     _jp.OverlayCapableJsPlumbUIComponent.applyType = function (component, t) {
7005         if (t.overlays) {
7006             // loop through the ones in the type. if already present on the component,
7007             // dont remove or re-add.
7008             var keep = {}, i;
7009 
7010             for (i in t.overlays) {
7011 
7012                 var existing = component._jsPlumb.overlays[t.overlays[i][1].id];
7013                 if (existing) {
7014                     // maybe update from data, if there were parameterised values for instance.
7015                     existing.updateFrom(t.overlays[i][1]);
7016                     keep[t.overlays[i][1].id] = true;
7017                 }
7018                 else {
7019                     var c = component.getCachedTypeItem("overlay", t.overlays[i][1].id);
7020                     if (c != null) {
7021                         c.reattach(component._jsPlumb.instance);
7022                         c.setVisible(true);
7023                         // maybe update from data, if there were parameterised values for instance.
7024                         c.updateFrom(t.overlays[i][1]);
7025                         component._jsPlumb.overlays[c.id] = c;
7026                     }
7027                     else {
7028                         c = component.addOverlay(t.overlays[i], true);
7029                     }
7030                     keep[c.id] = true;
7031                 }
7032             }
7033 
7034             // now loop through the full overlays and remove those that we dont want to keep
7035             for (i in component._jsPlumb.overlays) {
7036                 if (keep[component._jsPlumb.overlays[i].id] == null) {
7037                     component.removeOverlay(component._jsPlumb.overlays[i].id, true); // remove overlay but dont clean it up.
7038                     // that would remove event listeners etc; overlays are never discarded by the types stuff, they are
7039                     // just detached/reattached.
7040                 }
7041             }
7042         }
7043     };
7044 
7045     _ju.extend(_jp.OverlayCapableJsPlumbUIComponent, root.jsPlumbUIComponent, {
7046 
7047         setHover: function (hover, ignoreAttachedElements) {
7048             if (this._jsPlumb && !this._jsPlumb.instance.isConnectionBeingDragged()) {
7049                 for (var i in this._jsPlumb.overlays) {
7050                     this._jsPlumb.overlays[i][hover ? "addClass" : "removeClass"](this._jsPlumb.instance.hoverClass);
7051                 }
7052             }
7053         },
7054         addOverlay: function (overlay, doNotRepaint) {
7055             var o = _processOverlay(this, overlay);
7056             if (!doNotRepaint) {
7057                 this.repaint();
7058             }
7059             return o;
7060         },
7061         getOverlay: function (id) {
7062             return this._jsPlumb.overlays[id];
7063         },
7064         getOverlays: function () {
7065             return this._jsPlumb.overlays;
7066         },
7067         hideOverlay: function (id) {
7068             var o = this.getOverlay(id);
7069             if (o) {
7070                 o.hide();
7071             }
7072         },
7073         hideOverlays: function () {
7074             for (var i in this._jsPlumb.overlays) {
7075                 this._jsPlumb.overlays[i].hide();
7076             }
7077         },
7078         showOverlay: function (id) {
7079             var o = this.getOverlay(id);
7080             if (o) {
7081                 o.show();
7082             }
7083         },
7084         showOverlays: function () {
7085             for (var i in this._jsPlumb.overlays) {
7086                 this._jsPlumb.overlays[i].show();
7087             }
7088         },
7089         removeAllOverlays: function (doNotRepaint) {
7090             for (var i in this._jsPlumb.overlays) {
7091                 if (this._jsPlumb.overlays[i].cleanup) {
7092                     this._jsPlumb.overlays[i].cleanup();
7093                 }
7094             }
7095 
7096             this._jsPlumb.overlays = {};
7097             this._jsPlumb.overlayPositions = null;
7098             if (!doNotRepaint) {
7099                 this.repaint();
7100             }
7101         },
7102         removeOverlay: function (overlayId, dontCleanup) {
7103             var o = this._jsPlumb.overlays[overlayId];
7104             if (o) {
7105                 o.setVisible(false);
7106                 if (!dontCleanup && o.cleanup) {
7107                     o.cleanup();
7108                 }
7109                 delete this._jsPlumb.overlays[overlayId];
7110                 if (this._jsPlumb.overlayPositions) {
7111                     delete this._jsPlumb.overlayPositions[overlayId];
7112                 }
7113             }
7114         },
7115         removeOverlays: function () {
7116             for (var i = 0, j = arguments.length; i < j; i++) {
7117                 this.removeOverlay(arguments[i]);
7118             }
7119         },
7120         moveParent: function (newParent) {
7121             if (this.bgCanvas) {
7122                 this.bgCanvas.parentNode.removeChild(this.bgCanvas);
7123                 newParent.appendChild(this.bgCanvas);
7124             }
7125 
7126             if (this.canvas && this.canvas.parentNode) {
7127                 this.canvas.parentNode.removeChild(this.canvas);
7128                 newParent.appendChild(this.canvas);
7129 
7130                 for (var i in this._jsPlumb.overlays) {
7131                     if (this._jsPlumb.overlays[i].isAppendedAtTopLevel) {
7132                         var el = this._jsPlumb.overlays[i].getElement();
7133                         el.parentNode.removeChild(el);
7134                         newParent.appendChild(el);
7135                     }
7136                 }
7137             }
7138         },
7139         getLabel: function () {
7140             var lo = this.getOverlay(_internalLabelOverlayId);
7141             return lo != null ? lo.getLabel() : null;
7142         },
7143         getLabelOverlay: function () {
7144             return this.getOverlay(_internalLabelOverlayId);
7145         },
7146         setLabel: function (l) {
7147             var lo = this.getOverlay(_internalLabelOverlayId);
7148             if (!lo) {
7149                 var params = l.constructor === String || l.constructor === Function ? { label: l } : l;
7150                 lo = _makeLabelOverlay(this, params);
7151                 this._jsPlumb.overlays[_internalLabelOverlayId] = lo;
7152             }
7153             else {
7154                 if (l.constructor === String || l.constructor === Function) {
7155                     lo.setLabel(l);
7156                 }
7157                 else {
7158                     if (l.label) {
7159                         lo.setLabel(l.label);
7160                     }
7161                     if (l.location) {
7162                         lo.setLocation(l.location);
7163                     }
7164                 }
7165             }
7166 
7167             if (!this._jsPlumb.instance.isSuspendDrawing()) {
7168                 this.repaint();
7169             }
7170         },
7171         cleanup: function (force) {
7172             for (var i in this._jsPlumb.overlays) {
7173                 this._jsPlumb.overlays[i].cleanup(force);
7174                 this._jsPlumb.overlays[i].destroy(force);
7175             }
7176             if (force) {
7177                 this._jsPlumb.overlays = {};
7178                 this._jsPlumb.overlayPositions = null;
7179             }
7180         },
7181         setVisible: function (v) {
7182             this[v ? "showOverlays" : "hideOverlays"]();
7183         },
7184         setAbsoluteOverlayPosition: function (overlay, xy) {
7185             this._jsPlumb.overlayPositions[overlay.id] = xy;
7186         },
7187         getAbsoluteOverlayPosition: function (overlay) {
7188             return this._jsPlumb.overlayPositions ? this._jsPlumb.overlayPositions[overlay.id] : null;
7189         },
7190         _clazzManip:function(action, clazz, dontUpdateOverlays) {
7191             if (!dontUpdateOverlays) {
7192                 for (var i in this._jsPlumb.overlays) {
7193                     this._jsPlumb.overlays[i][action + "Class"](clazz);
7194                 }
7195             }
7196         },
7197         addClass:function(clazz, dontUpdateOverlays) {
7198             this._clazzManip("add", clazz, dontUpdateOverlays);
7199         },
7200         removeClass:function(clazz, dontUpdateOverlays) {
7201             this._clazzManip("remove", clazz, dontUpdateOverlays);
7202         }
7203     });
7204 
7205 // ------------------------------ END OverlayCapablejsPlumbUIComponent --------------------------------------------
7206 
7207 }).call(typeof window !== 'undefined' ? window : this);
7208 
7209 /*
7210  * jsPlumb Community Edition
7211  * 
7212  * Provides a way to visually connect elements on an HTML page, using SVG.
7213  * 
7214  * This file contains the code for Endpoints.
7215  *
7216  * Copyright (c) 2010 - 2017 jsPlumb (hello@jsplumbtoolkit.com)
7217  * 
7218  * https://jsplumbtoolkit.com
7219  * https://github.com/jsplumb/jsplumb
7220  * 
7221  * Dual licensed under the MIT and GPL2 licenses.
7222  */
7223 (function () {
7224 
7225     "use strict";
7226     var root = this, _jp = root.jsPlumb, _ju = root.jsPlumbUtil;
7227 
7228     // create the drag handler for a connection
7229     var _makeConnectionDragHandler = function (endpoint, placeholder, _jsPlumb) {
7230         var stopped = false;
7231         return {
7232             drag: function () {
7233                 if (stopped) {
7234                     stopped = false;
7235                     return true;
7236                 }
7237 
7238                 if (placeholder.element) {
7239                     var _ui = _jsPlumb.getUIPosition(arguments, _jsPlumb.getZoom());
7240                     if (_ui != null) {
7241                         _jsPlumb.setPosition(placeholder.element, _ui);
7242                     }
7243                     _jsPlumb.repaint(placeholder.element, _ui);
7244                     // always repaint the source endpoint, because only continuous/dynamic anchors cause the endpoint
7245                     // to be repainted, so static anchors need to be told (or the endpoint gets dragged around)
7246                     endpoint.paint({anchorPoint:endpoint.anchor.getCurrentLocation({element:endpoint})});
7247                 }
7248             },
7249             stopDrag: function () {
7250                 stopped = true;
7251             }
7252         };
7253     };
7254 
7255     // creates a placeholder div for dragging purposes, adds it, and pre-computes its offset.
7256     var _makeDraggablePlaceholder = function (placeholder, _jsPlumb, ipco, ips) {
7257         var n = _jsPlumb.createElement("div", { position : "absolute" });
7258         _jsPlumb.appendElement(n);
7259         var id = _jsPlumb.getId(n);
7260         _jsPlumb.setPosition(n, ipco);
7261         n.style.width = ips[0] + "px";
7262         n.style.height = ips[1] + "px";
7263         _jsPlumb.manage(id, n, true); // TRANSIENT MANAGE
7264         // create and assign an id, and initialize the offset.
7265         placeholder.id = id;
7266         placeholder.element = n;
7267     };
7268 
7269     // create a floating endpoint (for drag connections)
7270     var _makeFloatingEndpoint = function (paintStyle, referenceAnchor, endpoint, referenceCanvas, sourceElement, _jsPlumb, _newEndpoint, scope) {
7271         var floatingAnchor = new _jp.FloatingAnchor({ reference: referenceAnchor, referenceCanvas: referenceCanvas, jsPlumbInstance: _jsPlumb });
7272         //setting the scope here should not be the way to fix that mootools issue.  it should be fixed by not
7273         // adding the floating endpoint as a droppable.  that makes more sense anyway!
7274         // TRANSIENT MANAGE
7275         return _newEndpoint({
7276             paintStyle: paintStyle,
7277             endpoint: endpoint,
7278             anchor: floatingAnchor,
7279             source: sourceElement,
7280             scope: scope
7281         });
7282     };
7283 
7284     var typeParameters = [ "connectorStyle", "connectorHoverStyle", "connectorOverlays",
7285         "connector", "connectionType", "connectorClass", "connectorHoverClass" ];
7286 
7287     // a helper function that tries to find a connection to the given element, and returns it if so. if elementWithPrecedence is null,
7288     // or no connection to it is found, we return the first connection in our list.
7289     var findConnectionToUseForDynamicAnchor = function (ep, elementWithPrecedence) {
7290         var idx = 0;
7291         if (elementWithPrecedence != null) {
7292             for (var i = 0; i < ep.connections.length; i++) {
7293                 if (ep.connections[i].sourceId === elementWithPrecedence || ep.connections[i].targetId === elementWithPrecedence) {
7294                     idx = i;
7295                     break;
7296                 }
7297             }
7298         }
7299 
7300         return ep.connections[idx];
7301     };
7302 
7303     _jp.Endpoint = function (params) {
7304         var _jsPlumb = params._jsPlumb,
7305             _newConnection = params.newConnection,
7306             _newEndpoint = params.newEndpoint;
7307 
7308         this.idPrefix = "_jsplumb_e_";
7309         this.defaultLabelLocation = [ 0.5, 0.5 ];
7310         this.defaultOverlayKeys = ["Overlays", "EndpointOverlays"];
7311         _jp.OverlayCapableJsPlumbUIComponent.apply(this, arguments);
7312 
7313 // TYPE
7314 
7315         this.appendToDefaultType({
7316             connectionType:params.connectionType,
7317             maxConnections: params.maxConnections == null ? this._jsPlumb.instance.Defaults.MaxConnections : params.maxConnections, // maximum number of connections this endpoint can be the source of.,
7318             paintStyle: params.endpointStyle || params.paintStyle || params.style || this._jsPlumb.instance.Defaults.EndpointStyle || _jp.Defaults.EndpointStyle,
7319             hoverPaintStyle: params.endpointHoverStyle || params.hoverPaintStyle || this._jsPlumb.instance.Defaults.EndpointHoverStyle || _jp.Defaults.EndpointHoverStyle,
7320             connectorStyle: params.connectorStyle,
7321             connectorHoverStyle: params.connectorHoverStyle,
7322             connectorClass: params.connectorClass,
7323             connectorHoverClass: params.connectorHoverClass,
7324             connectorOverlays: params.connectorOverlays,
7325             connector: params.connector,
7326             connectorTooltip: params.connectorTooltip
7327         });
7328 
7329 // END TYPE
7330 
7331         this._jsPlumb.enabled = !(params.enabled === false);
7332         this._jsPlumb.visible = true;
7333         this.element = _jp.getElement(params.source);
7334         this._jsPlumb.uuid = params.uuid;
7335         this._jsPlumb.floatingEndpoint = null;
7336         var inPlaceCopy = null;
7337         if (this._jsPlumb.uuid) {
7338             params.endpointsByUUID[this._jsPlumb.uuid] = this;
7339         }
7340         this.elementId = params.elementId;
7341         this.dragProxy = params.dragProxy;
7342 
7343         this._jsPlumb.connectionCost = params.connectionCost;
7344         this._jsPlumb.connectionsDirected = params.connectionsDirected;
7345         this._jsPlumb.currentAnchorClass = "";
7346         this._jsPlumb.events = {};
7347 
7348         var deleteOnEmpty = params.deleteOnEmpty === true;
7349         this.setDeleteOnEmpty = function(d) { deleteOnEmpty = d; };
7350 
7351         var _updateAnchorClass = function () {
7352             // stash old, get new
7353             var oldAnchorClass = _jsPlumb.endpointAnchorClassPrefix + "-" + this._jsPlumb.currentAnchorClass;
7354             this._jsPlumb.currentAnchorClass = this.anchor.getCssClass();
7355             var anchorClass = _jsPlumb.endpointAnchorClassPrefix + (this._jsPlumb.currentAnchorClass ? "-" + this._jsPlumb.currentAnchorClass : "");
7356 
7357             this.removeClass(oldAnchorClass);
7358             this.addClass(anchorClass);
7359             // add and remove at the same time to reduce the number of reflows.
7360             _jp.updateClasses(this.element, anchorClass, oldAnchorClass);
7361         }.bind(this);
7362 
7363         this.prepareAnchor = function(anchorParams) {
7364             var a = this._jsPlumb.instance.makeAnchor(anchorParams, this.elementId, _jsPlumb);
7365             a.bind("anchorChanged", function (currentAnchor) {
7366                 this.fire("anchorChanged", {endpoint: this, anchor: currentAnchor});
7367                 _updateAnchorClass();
7368             }.bind(this));
7369             return a;
7370         };
7371 
7372         this.setPreparedAnchor = function(anchor, doNotRepaint) {
7373             this._jsPlumb.instance.continuousAnchorFactory.clear(this.elementId);
7374             this.anchor = anchor;
7375             _updateAnchorClass();
7376 
7377             if (!doNotRepaint) {
7378                 this._jsPlumb.instance.repaint(this.elementId);
7379             }
7380 
7381             return this;
7382         };
7383 
7384         this.setAnchor = function (anchorParams, doNotRepaint) {
7385             var a = this.prepareAnchor(anchorParams);
7386             this.setPreparedAnchor(a, doNotRepaint);
7387             return this;
7388         };
7389 
7390         var internalHover = function (state) {
7391             if (this.connections.length > 0) {
7392                 for (var i = 0; i < this.connections.length; i++) {
7393                     this.connections[i].setHover(state, false);
7394                 }
7395             }
7396             else {
7397                 this.setHover(state);
7398             }
7399         }.bind(this);
7400 
7401         this.bind("mouseover", function () {
7402             internalHover(true);
7403         });
7404         this.bind("mouseout", function () {
7405             internalHover(false);
7406         });
7407 
7408         // ANCHOR MANAGER
7409         if (!params._transient) { // in place copies, for example, are transient.  they will never need to be retrieved during a paint cycle, because they dont move, and then they are deleted.
7410             this._jsPlumb.instance.anchorManager.add(this, this.elementId);
7411         }
7412 
7413         this.prepareEndpoint = function(ep, typeId) {
7414             var _e = function (t, p) {
7415                 var rm = _jsPlumb.getRenderMode();
7416                 if (_jp.Endpoints[rm][t]) {
7417                     return new _jp.Endpoints[rm][t](p);
7418                 }
7419                 if (!_jsPlumb.Defaults.DoNotThrowErrors) {
7420                     throw { msg: "jsPlumb: unknown endpoint type '" + t + "'" };
7421                 }
7422             };
7423 
7424             var endpointArgs = {
7425                 _jsPlumb: this._jsPlumb.instance,
7426                 cssClass: params.cssClass,
7427                 container: params.container,
7428                 tooltip: params.tooltip,
7429                 connectorTooltip: params.connectorTooltip,
7430                 endpoint: this
7431             };
7432 
7433             var endpoint;
7434 
7435             if (_ju.isString(ep)) {
7436                 endpoint = _e(ep, endpointArgs);
7437             }
7438             else if (_ju.isArray(ep)) {
7439                 endpointArgs = _ju.merge(ep[1], endpointArgs);
7440                 endpoint = _e(ep[0], endpointArgs);
7441             }
7442             else {
7443                 endpoint = ep.clone();
7444             }
7445 
7446             // assign a clone function using a copy of endpointArgs. this is used when a drag starts: the endpoint that was dragged is cloned,
7447             // and the clone is left in its place while the original one goes off on a magical journey.
7448             // the copy is to get around a closure problem, in which endpointArgs ends up getting shared by
7449             // the whole world.
7450             //var argsForClone = jsPlumb.extend({}, endpointArgs);
7451             endpoint.clone = function () {
7452                 // TODO this, and the code above, can be refactored to be more dry.
7453                 if (_ju.isString(ep)) {
7454                     return _e(ep, endpointArgs);
7455                 }
7456                 else if (_ju.isArray(ep)) {
7457                     endpointArgs = _ju.merge(ep[1], endpointArgs);
7458                     return _e(ep[0], endpointArgs);
7459                 }
7460             }.bind(this);
7461 
7462             endpoint.typeId = typeId;
7463             return endpoint;
7464         };
7465 
7466         this.setEndpoint = function(ep, doNotRepaint) {
7467             var _ep = this.prepareEndpoint(ep);
7468             this.setPreparedEndpoint(_ep, true);
7469         };
7470 
7471         this.setPreparedEndpoint = function (ep, doNotRepaint) {
7472             if (this.endpoint != null) {
7473                 this.endpoint.cleanup();
7474                 this.endpoint.destroy();
7475             }
7476             this.endpoint = ep;
7477             this.type = this.endpoint.type;
7478             this.canvas = this.endpoint.canvas;
7479         };
7480 
7481         _jp.extend(this, params, typeParameters);
7482 
7483         this.isSource = params.isSource || false;
7484         this.isTemporarySource = params.isTemporarySource || false;
7485         this.isTarget = params.isTarget || false;
7486 
7487         this.connections = params.connections || [];
7488         this.connectorPointerEvents = params["connector-pointer-events"];
7489 
7490         this.scope = params.scope || _jsPlumb.getDefaultScope();
7491         this.timestamp = null;
7492         this.reattachConnections = params.reattach || _jsPlumb.Defaults.ReattachConnections;
7493         this.connectionsDetachable = _jsPlumb.Defaults.ConnectionsDetachable;
7494         if (params.connectionsDetachable === false || params.detachable === false) {
7495             this.connectionsDetachable = false;
7496         }
7497         this.dragAllowedWhenFull = params.dragAllowedWhenFull !== false;
7498 
7499         if (params.onMaxConnections) {
7500             this.bind("maxConnections", params.onMaxConnections);
7501         }
7502 
7503         //
7504         // add a connection. not part of public API.
7505         //
7506         this.addConnection = function (connection) {
7507             this.connections.push(connection);
7508             this[(this.connections.length > 0 ? "add" : "remove") + "Class"](_jsPlumb.endpointConnectedClass);
7509             this[(this.isFull() ? "add" : "remove") + "Class"](_jsPlumb.endpointFullClass);
7510         };
7511 
7512         this.detachFromConnection = function (connection, idx, doNotCleanup) {
7513             idx = idx == null ? this.connections.indexOf(connection) : idx;
7514             if (idx >= 0) {
7515                 this.connections.splice(idx, 1);
7516                 this[(this.connections.length > 0 ? "add" : "remove") + "Class"](_jsPlumb.endpointConnectedClass);
7517                 this[(this.isFull() ? "add" : "remove") + "Class"](_jsPlumb.endpointFullClass);
7518             }
7519 
7520             if (!doNotCleanup && deleteOnEmpty && this.connections.length === 0) {
7521                 _jsPlumb.deleteObject({
7522                     endpoint: this,
7523                     fireEvent: false,
7524                     deleteAttachedObjects: doNotCleanup !== true
7525                 });
7526             }
7527         };
7528 
7529         this.deleteEveryConnection = function(params) {
7530             var c = this.connections.length;
7531             for (var i = 0; i < c; i++) {
7532                 _jsPlumb.deleteConnection(this.connections[0], params);
7533             }
7534         };
7535 
7536         this.detachFrom = function (targetEndpoint, fireEvent, originalEvent) {
7537             var c = [];
7538             for (var i = 0; i < this.connections.length; i++) {
7539                 if (this.connections[i].endpoints[1] === targetEndpoint || this.connections[i].endpoints[0] === targetEndpoint) {
7540                     c.push(this.connections[i]);
7541                 }
7542             }
7543             for (var j = 0, count = c.length; j < count; j++) {
7544                 _jsPlumb.deleteConnection(c[0]);
7545             }
7546             return this;
7547         };
7548 
7549         this.getElement = function () {
7550             return this.element;
7551         };
7552 
7553         this.setElement = function (el) {
7554             var parentId = this._jsPlumb.instance.getId(el),
7555                 curId = this.elementId;
7556             // remove the endpoint from the list for the current endpoint's element
7557             _ju.removeWithFunction(params.endpointsByElement[this.elementId], function (e) {
7558                 return e.id === this.id;
7559             }.bind(this));
7560             this.element = _jp.getElement(el);
7561             this.elementId = _jsPlumb.getId(this.element);
7562             _jsPlumb.anchorManager.rehomeEndpoint(this, curId, this.element);
7563             _jsPlumb.dragManager.endpointAdded(this.element);
7564             _ju.addToList(params.endpointsByElement, parentId, this);
7565             return this;
7566         };
7567 
7568         /**
7569          * private but must be exposed.
7570          */
7571         this.makeInPlaceCopy = function () {
7572             var loc = this.anchor.getCurrentLocation({element: this}),
7573                 o = this.anchor.getOrientation(this),
7574                 acc = this.anchor.getCssClass(),
7575                 inPlaceAnchor = {
7576                     bind: function () {
7577                     },
7578                     compute: function () {
7579                         return [ loc[0], loc[1] ];
7580                     },
7581                     getCurrentLocation: function () {
7582                         return [ loc[0], loc[1] ];
7583                     },
7584                     getOrientation: function () {
7585                         return o;
7586                     },
7587                     getCssClass: function () {
7588                         return acc;
7589                     }
7590                 };
7591 
7592             return _newEndpoint({
7593                 dropOptions: params.dropOptions,
7594                 anchor: inPlaceAnchor,
7595                 source: this.element,
7596                 paintStyle: this.getPaintStyle(),
7597                 endpoint: params.hideOnDrag ? "Blank" : this.endpoint,
7598                 _transient: true,
7599                 scope: this.scope,
7600                 reference:this
7601             });
7602         };
7603 
7604         /**
7605          * returns a connection from the pool; used when dragging starts.  just gets the head of the array if it can.
7606          */
7607         this.connectorSelector = function () {
7608             var candidate = this.connections[0];
7609             if (candidate) {
7610                 return candidate;
7611             }
7612             else {
7613                 return (this.connections.length < this._jsPlumb.maxConnections) || this._jsPlumb.maxConnections === -1 ? null : candidate;
7614             }
7615         };
7616 
7617         this.setStyle = this.setPaintStyle;
7618 
7619         this.paint = function (params) {
7620             params = params || {};
7621             var timestamp = params.timestamp, recalc = !(params.recalc === false);
7622             if (!timestamp || this.timestamp !== timestamp) {
7623 
7624                 var info = _jsPlumb.updateOffset({ elId: this.elementId, timestamp: timestamp });
7625 
7626                 var xy = params.offset ? params.offset.o : info.o;
7627                 if (xy != null) {
7628                     var ap = params.anchorPoint, connectorPaintStyle = params.connectorPaintStyle;
7629                     if (ap == null) {
7630                         var wh = params.dimensions || info.s,
7631                             anchorParams = { xy: [ xy.left, xy.top ], wh: wh, element: this, timestamp: timestamp };
7632                         if (recalc && this.anchor.isDynamic && this.connections.length > 0) {
7633                             var c = findConnectionToUseForDynamicAnchor(this, params.elementWithPrecedence),
7634                                 oIdx = c.endpoints[0] === this ? 1 : 0,
7635                                 oId = oIdx === 0 ? c.sourceId : c.targetId,
7636                                 oInfo = _jsPlumb.getCachedData(oId),
7637                                 oOffset = oInfo.o, oWH = oInfo.s;
7638                             anchorParams.txy = [ oOffset.left, oOffset.top ];
7639                             anchorParams.twh = oWH;
7640                             anchorParams.tElement = c.endpoints[oIdx];
7641                         }
7642                         ap = this.anchor.compute(anchorParams);
7643                     }
7644 
7645                     this.endpoint.compute(ap, this.anchor.getOrientation(this), this._jsPlumb.paintStyleInUse, connectorPaintStyle || this.paintStyleInUse);
7646                     this.endpoint.paint(this._jsPlumb.paintStyleInUse, this.anchor);
7647                     this.timestamp = timestamp;
7648 
7649                     // paint overlays
7650                     for (var i in this._jsPlumb.overlays) {
7651                         if (this._jsPlumb.overlays.hasOwnProperty(i)) {
7652                             var o = this._jsPlumb.overlays[i];
7653                             if (o.isVisible()) {
7654                                 this._jsPlumb.overlayPlacements[i] = o.draw(this.endpoint, this._jsPlumb.paintStyleInUse);
7655                                 o.paint(this._jsPlumb.overlayPlacements[i]);
7656                             }
7657                         }
7658                     }
7659                 }
7660             }
7661         };
7662 
7663         this.getTypeDescriptor = function () {
7664             return "endpoint";
7665         };
7666         this.isVisible = function () {
7667             return this._jsPlumb.visible;
7668         };
7669 
7670         this.repaint = this.paint;
7671 
7672         var draggingInitialised = false;
7673         this.initDraggable = function () {
7674 
7675             // is this a connection source? we make it draggable and have the
7676             // drag listener maintain a connection with a floating endpoint.
7677             if (!draggingInitialised && _jp.isDragSupported(this.element)) {
7678                 var placeholderInfo = { id: null, element: null },
7679                     jpc = null,
7680                     existingJpc = false,
7681                     existingJpcParams = null,
7682                     _dragHandler = _makeConnectionDragHandler(this, placeholderInfo, _jsPlumb),
7683                     dragOptions = params.dragOptions || {},
7684                     defaultOpts = {},
7685                     startEvent = _jp.dragEvents.start,
7686                     stopEvent = _jp.dragEvents.stop,
7687                     dragEvent = _jp.dragEvents.drag,
7688                     beforeStartEvent = _jp.dragEvents.beforeStart,
7689                     payload;
7690 
7691                 // respond to beforeStart from katavorio; this will have, optionally, a payload of attribute values
7692                 // that were placed there by the makeSource mousedown listener.
7693                 var beforeStart = function(beforeStartParams) {
7694                     payload = beforeStartParams.e.payload || {};
7695                 };
7696 
7697                 var start = function (startParams) {
7698 
7699 // -------------   first, get a connection to drag. this may be null, in which case we are dragging a new one.
7700 
7701                     jpc = this.connectorSelector();
7702 
7703 // -------------------------------- now a bunch of tests about whether or not to proceed -------------------------
7704 
7705                     var _continue = true;
7706                     // if not enabled, return
7707                     if (!this.isEnabled()) {
7708                         _continue = false;
7709                     }
7710                     // if no connection and we're not a source - or temporarily a source, as is the case with makeSource - return.
7711                     if (jpc == null && !this.isSource && !this.isTemporarySource) {
7712                         _continue = false;
7713                     }
7714                     // otherwise if we're full and not allowed to drag, also return false.
7715                     if (this.isSource && this.isFull() && !(jpc != null && this.dragAllowedWhenFull)) {
7716                         _continue = false;
7717                     }
7718                     // if the connection was setup as not detachable or one of its endpoints
7719                     // was setup as connectionsDetachable = false, or Defaults.ConnectionsDetachable
7720                     // is set to false...
7721                     if (jpc != null && !jpc.isDetachable(this)) {
7722                         _continue = false;
7723                     }
7724 
7725                     var beforeDrag = _jsPlumb.checkCondition(jpc == null ? "beforeDrag" : "beforeStartDetach", {
7726                         endpoint:this,
7727                         source:this.element,
7728                         sourceId:this.elementId,
7729                         connection:jpc
7730                     });
7731                     if (beforeDrag === false) {
7732                         _continue = false;
7733                     }
7734                     // else we might have been given some data. we'll pass it in to a new connection as 'data'.
7735                     // here we also merge in the optional payload we were given on mousedown.
7736                     else if (typeof beforeDrag === "object") {
7737                         _jp.extend(beforeDrag, payload || {});
7738                     }
7739                     else {
7740                         // or if no beforeDrag data, maybe use the payload on its own.
7741                         beforeDrag = payload || {};
7742                     }
7743 
7744                     if (_continue === false) {
7745                         // this is for mootools and yui. returning false from this causes jquery to stop drag.
7746                         // the events are wrapped in both mootools and yui anyway, but i don't think returning
7747                         // false from the start callback would stop a drag.
7748                         if (_jsPlumb.stopDrag) {
7749                             _jsPlumb.stopDrag(this.canvas);
7750                         }
7751                         _dragHandler.stopDrag();
7752                         return false;
7753                     }
7754 
7755 // ---------------------------------------------------------------------------------------------------------------------
7756 
7757                     // ok to proceed.
7758 
7759                     // clear hover for all connections for this endpoint before continuing.
7760                     for (var i = 0; i < this.connections.length; i++) {
7761                         this.connections[i].setHover(false);
7762                     }
7763 
7764                     this.addClass("endpointDrag");
7765                     _jsPlumb.setConnectionBeingDragged(true);
7766 
7767                     // if we're not full but there was a connection, make it null. we'll create a new one.
7768                     if (jpc && !this.isFull() && this.isSource) {
7769                         jpc = null;
7770                     }
7771 
7772                     _jsPlumb.updateOffset({ elId: this.elementId });
7773 
7774 // ----------------    make the element we will drag around, and position it -----------------------------
7775 
7776                     var ipco = this._jsPlumb.instance.getOffset(this.canvas),
7777                         canvasElement = this.canvas,
7778                         ips = this._jsPlumb.instance.getSize(this.canvas);
7779 
7780                     _makeDraggablePlaceholder(placeholderInfo, _jsPlumb, ipco, ips);
7781 
7782                     // store the id of the dragging div and the source element. the drop function will pick these up.                   
7783                     _jsPlumb.setAttributes(this.canvas, {
7784                         "dragId": placeholderInfo.id,
7785                         "elId": this.elementId
7786                     });
7787 
7788 // ------------------- create an endpoint that will be our floating endpoint ------------------------------------
7789 
7790                     var endpointToFloat = this.dragProxy || this.endpoint;
7791                     if (this.dragProxy == null && this.connectionType != null) {
7792                         var aae = this._jsPlumb.instance.deriveEndpointAndAnchorSpec(this.connectionType);
7793                         if (aae.endpoints[1]) {
7794                             endpointToFloat = aae.endpoints[1];
7795                         }
7796                     }
7797                     var centerAnchor = this._jsPlumb.instance.makeAnchor("Center");
7798                     centerAnchor.isFloating = true;
7799                     this._jsPlumb.floatingEndpoint = _makeFloatingEndpoint(this.getPaintStyle(), centerAnchor, endpointToFloat, this.canvas, placeholderInfo.element, _jsPlumb, _newEndpoint, this.scope);
7800                     var _savedAnchor = this._jsPlumb.floatingEndpoint.anchor;
7801 
7802 
7803                     if (jpc == null) {
7804 
7805                         this.setHover(false, false);
7806                         // create a connection. one end is this endpoint, the other is a floating endpoint.                    
7807                         jpc = _newConnection({
7808                             sourceEndpoint: this,
7809                             targetEndpoint: this._jsPlumb.floatingEndpoint,
7810                             source: this.element,  // for makeSource with parent option.  ensure source element is represented correctly.
7811                             target: placeholderInfo.element,
7812                             anchors: [ this.anchor, this._jsPlumb.floatingEndpoint.anchor ],
7813                             paintStyle: params.connectorStyle, // this can be null. Connection will use the default.
7814                             hoverPaintStyle: params.connectorHoverStyle,
7815                             connector: params.connector, // this can also be null. Connection will use the default.
7816                             overlays: params.connectorOverlays,
7817                             type: this.connectionType,
7818                             cssClass: this.connectorClass,
7819                             hoverClass: this.connectorHoverClass,
7820                             scope:params.scope,
7821                             data:beforeDrag
7822                         });
7823                         jpc.pending = true;
7824                         jpc.addClass(_jsPlumb.draggingClass);
7825                         this._jsPlumb.floatingEndpoint.addClass(_jsPlumb.draggingClass);
7826                         this._jsPlumb.floatingEndpoint.anchor = _savedAnchor;
7827                         // fire an event that informs that a connection is being dragged
7828                         _jsPlumb.fire("connectionDrag", jpc);
7829 
7830                         // register the new connection on the drag manager. This connection, at this point, is 'pending',
7831                         // and has as its target a temporary element (the 'placeholder'). If the connection subsequently
7832                         // becomes established, the anchor manager is informed that the target of the connection has
7833                         // changed.
7834 
7835                         _jsPlumb.anchorManager.newConnection(jpc);
7836 
7837                     } else {
7838                         existingJpc = true;
7839                         jpc.setHover(false);
7840                         // new anchor idx
7841                         var anchorIdx = jpc.endpoints[0].id === this.id ? 0 : 1;
7842                         this.detachFromConnection(jpc, null, true);                         // detach from the connection while dragging is occurring. but dont cleanup automatically.
7843 
7844                         // store the original scope (issue 57)
7845                         var dragScope = _jsPlumb.getDragScope(canvasElement);
7846                         _jsPlumb.setAttribute(this.canvas, "originalScope", dragScope);
7847 
7848                         // fire an event that informs that a connection is being dragged. we do this before
7849                         // replacing the original target with the floating element info.
7850                         _jsPlumb.fire("connectionDrag", jpc);
7851 
7852                         // now we replace ourselves with the temporary div we created above:
7853                         if (anchorIdx === 0) {
7854                             existingJpcParams = [ jpc.source, jpc.sourceId, canvasElement, dragScope ];
7855                             _jsPlumb.anchorManager.sourceChanged(jpc.endpoints[anchorIdx].elementId, placeholderInfo.id, jpc, placeholderInfo.element);
7856 
7857                         } else {
7858                             existingJpcParams = [ jpc.target, jpc.targetId, canvasElement, dragScope ];
7859                             jpc.target = placeholderInfo.element;
7860                             jpc.targetId = placeholderInfo.id;
7861 
7862                             _jsPlumb.anchorManager.updateOtherEndpoint(jpc.sourceId, jpc.endpoints[anchorIdx].elementId, jpc.targetId, jpc);
7863                         }
7864 
7865                         // store the original endpoint and assign the new floating endpoint for the drag.
7866                         jpc.suspendedEndpoint = jpc.endpoints[anchorIdx];
7867 
7868                         // PROVIDE THE SUSPENDED ELEMENT, BE IT A SOURCE OR TARGET (ISSUE 39)
7869                         jpc.suspendedElement = jpc.endpoints[anchorIdx].getElement();
7870                         jpc.suspendedElementId = jpc.endpoints[anchorIdx].elementId;
7871                         jpc.suspendedElementType = anchorIdx === 0 ? "source" : "target";
7872 
7873                         jpc.suspendedEndpoint.setHover(false);
7874                         this._jsPlumb.floatingEndpoint.referenceEndpoint = jpc.suspendedEndpoint;
7875                         jpc.endpoints[anchorIdx] = this._jsPlumb.floatingEndpoint;
7876 
7877                         jpc.addClass(_jsPlumb.draggingClass);
7878                         this._jsPlumb.floatingEndpoint.addClass(_jsPlumb.draggingClass);
7879                     }
7880 
7881                     // register it and register connection on it.
7882                     _jsPlumb.floatingConnections[placeholderInfo.id] = jpc;
7883                     // only register for the target endpoint; we will not be dragging the source at any time
7884                     // before this connection is either discarded or made into a permanent connection.
7885                     _ju.addToList(params.endpointsByElement, placeholderInfo.id, this._jsPlumb.floatingEndpoint);
7886                     // tell jsplumb about it
7887                     _jsPlumb.currentlyDragging = true;
7888                 }.bind(this);
7889 
7890                 var stop = function () {
7891                     _jsPlumb.setConnectionBeingDragged(false);
7892 
7893                     if (jpc && jpc.endpoints != null) {
7894                         // get the actual drop event (decode from library args to stop function)
7895                         var originalEvent = _jsPlumb.getDropEvent(arguments);
7896                         // unlock the other endpoint (if it is dynamic, it would have been locked at drag start)
7897                         var idx = _jsPlumb.getFloatingAnchorIndex(jpc);
7898                         jpc.endpoints[idx === 0 ? 1 : 0].anchor.locked = false;
7899                         // TODO: Dont want to know about css classes inside jsplumb, ideally.
7900                         jpc.removeClass(_jsPlumb.draggingClass);
7901 
7902                         // if we have the floating endpoint then the connection has not been dropped
7903                         // on another endpoint.  If it is a new connection we throw it away. If it is an
7904                         // existing connection we check to see if we should reattach it, throwing it away
7905                         // if not.
7906                         if (this._jsPlumb && (jpc.deleteConnectionNow || jpc.endpoints[idx] === this._jsPlumb.floatingEndpoint)) {
7907                             // 6a. if the connection was an existing one...
7908                             if (existingJpc && jpc.suspendedEndpoint) {
7909                                 // fix for issue35, thanks Sylvain Gizard: when firing the detach event make sure the
7910                                 // floating endpoint has been replaced.
7911                                 if (idx === 0) {
7912                                     jpc.floatingElement = jpc.source;
7913                                     jpc.floatingId = jpc.sourceId;
7914                                     jpc.floatingEndpoint = jpc.endpoints[0];
7915                                     jpc.floatingIndex = 0;
7916                                     jpc.source = existingJpcParams[0];
7917                                     jpc.sourceId = existingJpcParams[1];
7918                                 } else {
7919                                     // keep a copy of the floating element; the anchor manager will want to clean up.
7920                                     jpc.floatingElement = jpc.target;
7921                                     jpc.floatingId = jpc.targetId;
7922                                     jpc.floatingEndpoint = jpc.endpoints[1];
7923                                     jpc.floatingIndex = 1;
7924                                     jpc.target = existingJpcParams[0];
7925                                     jpc.targetId = existingJpcParams[1];
7926                                 }
7927 
7928                                 var fe = this._jsPlumb.floatingEndpoint; // store for later removal.
7929                                 // restore the original scope (issue 57)
7930                                 _jsPlumb.setDragScope(existingJpcParams[2], existingJpcParams[3]);
7931                                 jpc.endpoints[idx] = jpc.suspendedEndpoint;
7932                                 // IF the connection should be reattached, or the other endpoint refuses detach, then
7933                                 // reset the connection to its original state
7934                                 //if (jpc.isReattach() || jpc._forceReattach || jpc._forceDetach || !jpc.endpoints[idx === 0 ? 1 : 0].detach({connection:jpc, ignoreTarget:false, forceDetach:false, fireEvent:true, originalEvent:originalEvent, endpointBeingDeleted:true})) {
7935                                 if (jpc.isReattach() || jpc._forceReattach || jpc._forceDetach || !_jsPlumb.deleteConnection(jpc)) {
7936 
7937                                     jpc.setHover(false);
7938                                     jpc._forceDetach = null;
7939                                     jpc._forceReattach = null;
7940                                     this._jsPlumb.floatingEndpoint.detachFromConnection(jpc);
7941                                     jpc.suspendedEndpoint.addConnection(jpc);
7942 
7943                                     // TODO this code is duplicated in lots of places...and there is nothing external
7944                                     // in the code; it all refers to the connection itself. we could add a
7945                                     // `checkSanity(connection)` method to anchorManager that did this.
7946                                     if (idx === 1) {
7947                                         _jsPlumb.anchorManager.updateOtherEndpoint(jpc.sourceId, jpc.floatingId, jpc.targetId, jpc);
7948                                     }
7949                                     else {
7950                                         _jsPlumb.anchorManager.sourceChanged(jpc.floatingId, jpc.sourceId, jpc, jpc.source);
7951                                     }
7952 
7953                                     _jsPlumb.repaint(existingJpcParams[1]);
7954                                 }
7955                                 else {
7956                                     _jsPlumb.deleteObject({endpoint: fe});
7957                                 }
7958                             }
7959                         }
7960 
7961                         // makeTargets sets this flag, to tell us we have been replaced and should delete this object.
7962                         if (this.deleteAfterDragStop) {
7963                             _jsPlumb.deleteObject({endpoint: this});
7964                         }
7965                         else {
7966                             if (this._jsPlumb) {
7967                                  this.paint({recalc: false});
7968                             }
7969                         }
7970 
7971                         // although the connection is no longer valid, there are use cases where this is useful.
7972                         _jsPlumb.fire("connectionDragStop", jpc, originalEvent);
7973                         // fire this event to give people more fine-grained control (connectionDragStop fires a lot)
7974                         if (jpc.pending) {
7975                             _jsPlumb.fire("connectionAborted", jpc, originalEvent);
7976                         }
7977                         // tell jsplumb that dragging is finished.
7978                         _jsPlumb.currentlyDragging = false;
7979                         jpc.suspendedElement = null;
7980                         jpc.suspendedEndpoint = null;
7981                         jpc = null;
7982                     }
7983 
7984                     // if no endpoints, jpc already cleaned up. but still we want to ensure we're reset properly.
7985                     // remove the element associated with the floating endpoint
7986                     // (and its associated floating endpoint and visual artefacts)
7987                     if (placeholderInfo && placeholderInfo.element) {
7988                         _jsPlumb.remove(placeholderInfo.element, false, false);
7989                     }
7990                     // remove the inplace copy
7991                     if (inPlaceCopy) {
7992                         _jsPlumb.deleteObject({endpoint: inPlaceCopy});
7993                     }
7994 
7995                     if (this._jsPlumb) {
7996                         // make our canvas visible (TODO: hand off to library; we should not know about DOM)
7997                         this.canvas.style.visibility = "visible";
7998                         // unlock our anchor
7999                         this.anchor.locked = false;
8000                         // clear floating anchor.
8001                         this._jsPlumb.floatingEndpoint = null;
8002                     }
8003 
8004                 }.bind(this);
8005 
8006                 dragOptions = _jp.extend(defaultOpts, dragOptions);
8007                 dragOptions.scope = this.scope || dragOptions.scope;
8008                 dragOptions[beforeStartEvent] = _ju.wrap(dragOptions[beforeStartEvent], beforeStart, false);
8009                 dragOptions[startEvent] = _ju.wrap(dragOptions[startEvent], start, false);
8010                 // extracted drag handler function so can be used by makeSource
8011                 dragOptions[dragEvent] = _ju.wrap(dragOptions[dragEvent], _dragHandler.drag);
8012                 dragOptions[stopEvent] = _ju.wrap(dragOptions[stopEvent], stop);
8013                 dragOptions.multipleDrop = false;
8014 
8015                 dragOptions.canDrag = function () {
8016                     return this.isSource || this.isTemporarySource || /*(this.isTarget && */this.connections.length > 0/*)*/;
8017                 }.bind(this);
8018 
8019                 _jsPlumb.initDraggable(this.canvas, dragOptions, "internal");
8020 
8021                 this.canvas._jsPlumbRelatedElement = this.element;
8022 
8023                 draggingInitialised = true;
8024             }
8025         };
8026 
8027         var ep = params.endpoint || this._jsPlumb.instance.Defaults.Endpoint || _jp.Defaults.Endpoint;
8028         this.setEndpoint(ep, true);
8029         var anchorParamsToUse = params.anchor ? params.anchor : params.anchors ? params.anchors : (_jsPlumb.Defaults.Anchor || "Top");
8030         this.setAnchor(anchorParamsToUse, true);
8031 
8032         // finally, set type if it was provided
8033         var type = [ "default", (params.type || "")].join(" ");
8034         this.addType(type, params.data, true);
8035         this.canvas = this.endpoint.canvas;
8036         this.canvas._jsPlumb = this;
8037 
8038         this.initDraggable();
8039 
8040         // pulled this out into a function so we can reuse it for the inPlaceCopy canvas; you can now drop detached connections
8041         // back onto the endpoint you detached it from.
8042         var _initDropTarget = function (canvas, isTransient, endpoint, referenceEndpoint) {
8043 
8044             if (_jp.isDropSupported(this.element)) {
8045                 var dropOptions = params.dropOptions || _jsPlumb.Defaults.DropOptions || _jp.Defaults.DropOptions;
8046                 dropOptions = _jp.extend({}, dropOptions);
8047                 dropOptions.scope = dropOptions.scope || this.scope;
8048                 var dropEvent = _jp.dragEvents.drop,
8049                     overEvent = _jp.dragEvents.over,
8050                     outEvent = _jp.dragEvents.out,
8051                     _ep = this,
8052                     drop = _jsPlumb.EndpointDropHandler({
8053                         getEndpoint: function () {
8054                             return _ep;
8055                         },
8056                         jsPlumb: _jsPlumb,
8057                         enabled: function () {
8058                             return endpoint != null ? endpoint.isEnabled() : true;
8059                         },
8060                         isFull: function () {
8061                             return endpoint.isFull();
8062                         },
8063                         element: this.element,
8064                         elementId: this.elementId,
8065                         isSource: this.isSource,
8066                         isTarget: this.isTarget,
8067                         addClass: function (clazz) {
8068                             _ep.addClass(clazz);
8069                         },
8070                         removeClass: function (clazz) {
8071                             _ep.removeClass(clazz);
8072                         },
8073                         isDropAllowed: function () {
8074                             return _ep.isDropAllowed.apply(_ep, arguments);
8075                         },
8076                         reference:referenceEndpoint,
8077                         isRedrop:function(jpc, dhParams) {
8078                             return jpc.suspendedEndpoint && dhParams.reference && (jpc.suspendedEndpoint.id === dhParams.reference.id);
8079                         }
8080                     });
8081 
8082                 dropOptions[dropEvent] = _ju.wrap(dropOptions[dropEvent], drop, true);
8083                 dropOptions[overEvent] = _ju.wrap(dropOptions[overEvent], function () {
8084                     var draggable = _jp.getDragObject(arguments),
8085                         id = _jsPlumb.getAttribute(_jp.getElement(draggable), "dragId"),
8086                         _jpc = _jsPlumb.floatingConnections[id];
8087 
8088                     if (_jpc != null) {
8089                         var idx = _jsPlumb.getFloatingAnchorIndex(_jpc);
8090                         // here we should fire the 'over' event if we are a target and this is a new connection,
8091                         // or we are the same as the floating endpoint.
8092                         var _cont = (this.isTarget && idx !== 0) || (_jpc.suspendedEndpoint && this.referenceEndpoint && this.referenceEndpoint.id === _jpc.suspendedEndpoint.id);
8093                         if (_cont) {
8094                             var bb = _jsPlumb.checkCondition("checkDropAllowed", {
8095                                 sourceEndpoint: _jpc.endpoints[idx],
8096                                 targetEndpoint: this,
8097                                 connection: _jpc
8098                             });
8099                             this[(bb ? "add" : "remove") + "Class"](_jsPlumb.endpointDropAllowedClass);
8100                             this[(bb ? "remove" : "add") + "Class"](_jsPlumb.endpointDropForbiddenClass);
8101                             _jpc.endpoints[idx].anchor.over(this.anchor, this);
8102                         }
8103                     }
8104                 }.bind(this));
8105 
8106                 dropOptions[outEvent] = _ju.wrap(dropOptions[outEvent], function () {
8107                     var draggable = _jp.getDragObject(arguments),
8108                         id = draggable == null ? null : _jsPlumb.getAttribute(_jp.getElement(draggable), "dragId"),
8109                         _jpc = id ? _jsPlumb.floatingConnections[id] : null;
8110 
8111                     if (_jpc != null) {
8112                         var idx = _jsPlumb.getFloatingAnchorIndex(_jpc);
8113                         var _cont = (this.isTarget && idx !== 0) || (_jpc.suspendedEndpoint && this.referenceEndpoint && this.referenceEndpoint.id === _jpc.suspendedEndpoint.id);
8114                         if (_cont) {
8115                             this.removeClass(_jsPlumb.endpointDropAllowedClass);
8116                             this.removeClass(_jsPlumb.endpointDropForbiddenClass);
8117                             _jpc.endpoints[idx].anchor.out();
8118                         }
8119                     }
8120                 }.bind(this));
8121 
8122                 _jsPlumb.initDroppable(canvas, dropOptions, "internal", isTransient);
8123             }
8124         }.bind(this);
8125 
8126         // Initialise the endpoint's canvas as a drop target. The drop handler will take care of the logic of whether
8127         // something can actually be dropped.
8128         if (!this.anchor.isFloating) {
8129             _initDropTarget(this.canvas, !(params._transient || this.anchor.isFloating), this, params.reference);
8130         }
8131 
8132         return this;
8133     };
8134 
8135     _ju.extend(_jp.Endpoint, _jp.OverlayCapableJsPlumbUIComponent, {
8136 
8137         setVisible: function (v, doNotChangeConnections, doNotNotifyOtherEndpoint) {
8138             this._jsPlumb.visible = v;
8139             if (this.canvas) {
8140                 this.canvas.style.display = v ? "block" : "none";
8141             }
8142             this[v ? "showOverlays" : "hideOverlays"]();
8143             if (!doNotChangeConnections) {
8144                 for (var i = 0; i < this.connections.length; i++) {
8145                     this.connections[i].setVisible(v);
8146                     if (!doNotNotifyOtherEndpoint) {
8147                         var oIdx = this === this.connections[i].endpoints[0] ? 1 : 0;
8148                         // only change the other endpoint if this is its only connection.
8149                         if (this.connections[i].endpoints[oIdx].connections.length === 1) {
8150                             this.connections[i].endpoints[oIdx].setVisible(v, true, true);
8151                         }
8152                     }
8153                 }
8154             }
8155         },
8156         getAttachedElements: function () {
8157             return this.connections;
8158         },
8159         applyType: function (t, doNotRepaint) {
8160             this.setPaintStyle(t.endpointStyle || t.paintStyle, doNotRepaint);
8161             this.setHoverPaintStyle(t.endpointHoverStyle || t.hoverPaintStyle, doNotRepaint);
8162             if (t.maxConnections != null) {
8163                 this._jsPlumb.maxConnections = t.maxConnections;
8164             }
8165             if (t.scope) {
8166                 this.scope = t.scope;
8167             }
8168             _jp.extend(this, t, typeParameters);
8169             if (t.cssClass != null && this.canvas) {
8170                 this._jsPlumb.instance.addClass(this.canvas, t.cssClass);
8171             }
8172             _jp.OverlayCapableJsPlumbUIComponent.applyType(this, t);
8173         },
8174         isEnabled: function () {
8175             return this._jsPlumb.enabled;
8176         },
8177         setEnabled: function (e) {
8178             this._jsPlumb.enabled = e;
8179         },
8180         cleanup: function () {
8181             var anchorClass = this._jsPlumb.instance.endpointAnchorClassPrefix + (this._jsPlumb.currentAnchorClass ? "-" + this._jsPlumb.currentAnchorClass : "");
8182             _jp.removeClass(this.element, anchorClass);
8183             this.anchor = null;
8184             this.endpoint.cleanup(true);
8185             this.endpoint.destroy();
8186             this.endpoint = null;
8187             // drag/drop
8188             this._jsPlumb.instance.destroyDraggable(this.canvas, "internal");
8189             this._jsPlumb.instance.destroyDroppable(this.canvas, "internal");
8190         },
8191         setHover: function (h) {
8192             if (this.endpoint && this._jsPlumb && !this._jsPlumb.instance.isConnectionBeingDragged()) {
8193                 this.endpoint.setHover(h);
8194             }
8195         },
8196         isFull: function () {
8197             return this._jsPlumb.maxConnections === 0 ? true : !(this.isFloating() || this._jsPlumb.maxConnections < 0 || this.connections.length < this._jsPlumb.maxConnections);
8198         },
8199         /**
8200          * private but needs to be exposed.
8201          */
8202         isFloating: function () {
8203             return this.anchor != null && this.anchor.isFloating;
8204         },
8205         isConnectedTo: function (endpoint) {
8206             var found = false;
8207             if (endpoint) {
8208                 for (var i = 0; i < this.connections.length; i++) {
8209                     if (this.connections[i].endpoints[1] === endpoint || this.connections[i].endpoints[0] === endpoint) {
8210                         found = true;
8211                         break;
8212                     }
8213                 }
8214             }
8215             return found;
8216         },
8217         getConnectionCost: function () {
8218             return this._jsPlumb.connectionCost;
8219         },
8220         setConnectionCost: function (c) {
8221             this._jsPlumb.connectionCost = c;
8222         },
8223         areConnectionsDirected: function () {
8224             return this._jsPlumb.connectionsDirected;
8225         },
8226         setConnectionsDirected: function (b) {
8227             this._jsPlumb.connectionsDirected = b;
8228         },
8229         setElementId: function (_elId) {
8230             this.elementId = _elId;
8231             this.anchor.elementId = _elId;
8232         },
8233         setReferenceElement: function (_el) {
8234             this.element = _jp.getElement(_el);
8235         },
8236         setDragAllowedWhenFull: function (allowed) {
8237             this.dragAllowedWhenFull = allowed;
8238         },
8239         equals: function (endpoint) {
8240             return this.anchor.equals(endpoint.anchor);
8241         },
8242         getUuid: function () {
8243             return this._jsPlumb.uuid;
8244         },
8245         computeAnchor: function (params) {
8246             return this.anchor.compute(params);
8247         }
8248     });
8249 
8250     root.jsPlumbInstance.prototype.EndpointDropHandler = function (dhParams) {
8251         return function (e) {
8252 
8253             var _jsPlumb = dhParams.jsPlumb;
8254 
8255             // remove the classes that are added dynamically. drop is neither forbidden nor allowed now that
8256             // the drop is finishing.
8257             dhParams.removeClass(_jsPlumb.endpointDropAllowedClass);
8258             dhParams.removeClass(_jsPlumb.endpointDropForbiddenClass);
8259 
8260             var originalEvent = _jsPlumb.getDropEvent(arguments),
8261                 draggable = _jsPlumb.getDragObject(arguments),
8262                 id = _jsPlumb.getAttribute(draggable, "dragId"),
8263                 elId = _jsPlumb.getAttribute(draggable, "elId"),
8264                 scope = _jsPlumb.getAttribute(draggable, "originalScope"),
8265                 jpc = _jsPlumb.floatingConnections[id];
8266 
8267             // if no active connection, bail.
8268             if (jpc == null) {
8269                 return;
8270             }
8271 
8272             // calculate if this is an existing connection.
8273             var existingConnection = jpc.suspendedEndpoint != null;
8274 
8275             // if suspended endpoint exists but has been cleaned up, bail. This means it's an existing connection
8276             // that has been detached and will shortly be discarded.
8277             if (existingConnection && jpc.suspendedEndpoint._jsPlumb == null) {
8278                 return;
8279             }
8280 
8281             // get the drop endpoint. for a normal connection this is just the one that would replace the currently
8282             // floating endpoint. for a makeTarget this is a new endpoint that is created on drop. But we leave that to
8283             // the handler to figure out.
8284             var _ep = dhParams.getEndpoint(jpc);
8285 
8286             // If we're not given an endpoint to use, bail.
8287             if (_ep == null) {
8288                 return;
8289             }
8290 
8291             // if this is a drop back where the connection came from, mark it force reattach and
8292             // return; the stop handler will reattach. without firing an event.
8293             if (dhParams.isRedrop(jpc, dhParams)) {
8294                 jpc._forceReattach = true;
8295                 jpc.setHover(false);
8296                 if (dhParams.maybeCleanup) {
8297                     dhParams.maybeCleanup(_ep);
8298                 }
8299                 return;
8300             }
8301 
8302             // ensure we dont bother trying to drop sources on non-source eps, and same for target.
8303             var idx = _jsPlumb.getFloatingAnchorIndex(jpc);
8304             if ((idx === 0 && !dhParams.isSource)|| (idx === 1 && !dhParams.isTarget)){
8305                 if (dhParams.maybeCleanup) {
8306                     dhParams.maybeCleanup(_ep);
8307                 }
8308                 return;
8309             }
8310 
8311             if (dhParams.onDrop) {
8312                 dhParams.onDrop(jpc);
8313             }
8314 
8315             // restore the original scope if necessary (issue 57)
8316             if (scope) {
8317                 _jsPlumb.setDragScope(draggable, scope);
8318             }
8319 
8320             // if the target of the drop is full, fire an event (we abort below)
8321             // makeTarget: keep.
8322             var isFull = dhParams.isFull(e);
8323             if (isFull) {
8324                 _ep.fire("maxConnections", {
8325                     endpoint: this,
8326                     connection: jpc,
8327                     maxConnections: _ep._jsPlumb.maxConnections
8328                 }, originalEvent);
8329             }
8330             //
8331             // if endpoint enabled, not full, and matches the index of the floating endpoint...
8332             if (!isFull &&  dhParams.enabled()) {
8333                 var _doContinue = true;
8334 
8335                 // before testing for beforeDrop, reset the connection's source/target to be the actual DOM elements
8336                 // involved (that is, stash any temporary stuff used for dragging. but we need to keep it around in
8337                 // order that the anchor manager can clean things up properly).
8338                 if (idx === 0) {
8339                     jpc.floatingElement = jpc.source;
8340                     jpc.floatingId = jpc.sourceId;
8341                     jpc.floatingEndpoint = jpc.endpoints[0];
8342                     jpc.floatingIndex = 0;
8343                     jpc.source = dhParams.element;
8344                     jpc.sourceId = dhParams.elementId;
8345                 } else {
8346                     jpc.floatingElement = jpc.target;
8347                     jpc.floatingId = jpc.targetId;
8348                     jpc.floatingEndpoint = jpc.endpoints[1];
8349                     jpc.floatingIndex = 1;
8350                     jpc.target = dhParams.element;
8351                     jpc.targetId = dhParams.elementId;
8352                 }
8353 
8354                 // if this is an existing connection and detach is not allowed we won't continue. The connection's
8355                 // endpoints have been reinstated; everything is back to how it was.
8356                 if (existingConnection && jpc.suspendedEndpoint.id !== _ep.id) {
8357                     if (!jpc.isDetachAllowed(jpc) || !jpc.endpoints[idx].isDetachAllowed(jpc) || !jpc.suspendedEndpoint.isDetachAllowed(jpc) || !_jsPlumb.checkCondition("beforeDetach", jpc)) {
8358                         _doContinue = false;
8359                     }
8360                 }
8361 
8362 // ------------ wrap the execution path in a function so we can support asynchronous beforeDrop
8363 
8364                 var continueFunction = function (optionalData) {
8365                     // remove this jpc from the current endpoint, which is a floating endpoint that we will
8366                     // subsequently discard.
8367                     jpc.endpoints[idx].detachFromConnection(jpc);
8368 
8369                     // if there's a suspended endpoint, detach it from the connection.
8370                     if (jpc.suspendedEndpoint) {
8371                         jpc.suspendedEndpoint.detachFromConnection(jpc);
8372                     }
8373 
8374                     jpc.endpoints[idx] = _ep;
8375                     _ep.addConnection(jpc);
8376 
8377                     // copy our parameters in to the connection:
8378                     var params = _ep.getParameters();
8379                     for (var aParam in params) {
8380                         jpc.setParameter(aParam, params[aParam]);
8381                     }
8382 
8383                     if (!existingConnection) {
8384                         // if not an existing connection and
8385                         if (params.draggable) {
8386                             _jsPlumb.initDraggable(this.element, dhParams.dragOptions, "internal", _jsPlumb);
8387                         }
8388                     }
8389                     else {
8390                         var suspendedElementId = jpc.suspendedEndpoint.elementId;
8391                         _jsPlumb.fireMoveEvent({
8392                             index: idx,
8393                             originalSourceId: idx === 0 ? suspendedElementId : jpc.sourceId,
8394                             newSourceId: idx === 0 ? _ep.elementId : jpc.sourceId,
8395                             originalTargetId: idx === 1 ? suspendedElementId : jpc.targetId,
8396                             newTargetId: idx === 1 ? _ep.elementId : jpc.targetId,
8397                             originalSourceEndpoint: idx === 0 ? jpc.suspendedEndpoint : jpc.endpoints[0],
8398                             newSourceEndpoint: idx === 0 ? _ep : jpc.endpoints[0],
8399                             originalTargetEndpoint: idx === 1 ? jpc.suspendedEndpoint : jpc.endpoints[1],
8400                             newTargetEndpoint: idx === 1 ? _ep : jpc.endpoints[1],
8401                             connection: jpc
8402                         }, originalEvent);
8403                     }
8404 
8405                     if (idx === 1) {
8406                         _jsPlumb.anchorManager.updateOtherEndpoint(jpc.sourceId, jpc.floatingId, jpc.targetId, jpc);
8407                     }
8408                     else {
8409                         _jsPlumb.anchorManager.sourceChanged(jpc.floatingId, jpc.sourceId, jpc, jpc.source);
8410                     }
8411 
8412                     // when makeSource has uniqueEndpoint:true, we want to create connections with new endpoints
8413                     // that are subsequently deleted. So makeSource sets `finalEndpoint`, which is the Endpoint to
8414                     // which the connection should be attached. The `detachFromConnection` call below results in the
8415                     // temporary endpoint being cleaned up.
8416                     if (jpc.endpoints[0].finalEndpoint) {
8417                         var _toDelete = jpc.endpoints[0];
8418                         _toDelete.detachFromConnection(jpc);
8419                         jpc.endpoints[0] = jpc.endpoints[0].finalEndpoint;
8420                         jpc.endpoints[0].addConnection(jpc);
8421                     }
8422 
8423                     // if optionalData was given, merge it onto the connection's data.
8424                     if (_ju.isObject(optionalData)) {
8425                         jpc.mergeData(optionalData);
8426                     }
8427                     // finalise will inform the anchor manager and also add to
8428                     // connectionsByScope if necessary.
8429                     _jsPlumb.finaliseConnection(jpc, null, originalEvent, false);
8430                     jpc.setHover(false);
8431 
8432                 }.bind(this);
8433 
8434                 var dontContinueFunction = function () {
8435                     // otherwise just put it back on the endpoint it was on before the drag.
8436                     if (jpc.suspendedEndpoint) {
8437                         jpc.endpoints[idx] = jpc.suspendedEndpoint;
8438                         jpc.setHover(false);
8439                         jpc._forceDetach = true;
8440                         if (idx === 0) {
8441                             jpc.source = jpc.suspendedEndpoint.element;
8442                             jpc.sourceId = jpc.suspendedEndpoint.elementId;
8443                         } else {
8444                             jpc.target = jpc.suspendedEndpoint.element;
8445                             jpc.targetId = jpc.suspendedEndpoint.elementId;
8446                         }
8447                         jpc.suspendedEndpoint.addConnection(jpc);
8448 
8449                         // TODO checkSanity
8450                         if (idx === 1) {
8451                             _jsPlumb.anchorManager.updateOtherEndpoint(jpc.sourceId, jpc.floatingId, jpc.targetId, jpc);
8452                         }
8453                         else {
8454                             _jsPlumb.anchorManager.sourceChanged(jpc.floatingId, jpc.sourceId, jpc, jpc.source);
8455                         }
8456 
8457                         _jsPlumb.repaint(jpc.sourceId);
8458                         jpc._forceDetach = false;
8459                     }
8460                 };
8461 
8462 // --------------------------------------
8463                 // now check beforeDrop.  this will be available only on Endpoints that are setup to
8464                 // have a beforeDrop condition (although, secretly, under the hood all Endpoints and
8465                 // the Connection have them, because they are on jsPlumbUIComponent.  shhh!), because
8466                 // it only makes sense to have it on a target endpoint.
8467                 _doContinue = _doContinue && dhParams.isDropAllowed(jpc.sourceId, jpc.targetId, jpc.scope, jpc, _ep);// && jpc.pending;
8468 
8469                 if (_doContinue) {
8470                     continueFunction(_doContinue);
8471                     return true;
8472                 }
8473                 else {
8474                     dontContinueFunction();
8475                 }
8476             }
8477 
8478             if (dhParams.maybeCleanup) {
8479                 dhParams.maybeCleanup(_ep);
8480             }
8481 
8482             _jsPlumb.currentlyDragging = false;
8483         };
8484     };
8485 }).call(typeof window !== 'undefined' ? window : this);
8486 
8487 /*
8488  * jsPlumb Community Edition
8489  * 
8490  * Provides a way to visually connect elements on an HTML page, using SVG.
8491  * 
8492  * This file contains the code for Connections.
8493  *
8494  * Copyright (c) 2010 - 2017 jsPlumb (hello@jsplumbtoolkit.com)
8495  * 
8496  * https://jsplumbtoolkit.com
8497  * https://github.com/jsplumb/jsplumb
8498  * 
8499  * Dual licensed under the MIT and GPL2 licenses.
8500  */
8501 ;
8502 (function () {
8503 
8504     "use strict";
8505     var root = this,
8506         _jp = root.jsPlumb,
8507         _ju = root.jsPlumbUtil;
8508 
8509     var makeConnector = function (_jsPlumb, renderMode, connectorName, connectorArgs, forComponent) {
8510             if (!_jsPlumb.Defaults.DoNotThrowErrors && _jp.Connectors[renderMode][connectorName] == null) {
8511                 throw { msg: "jsPlumb: unknown connector type '" + connectorName + "'" };
8512             }
8513 
8514             return new _jp.Connectors[renderMode][connectorName](connectorArgs, forComponent);
8515         },
8516         _makeAnchor = function (anchorParams, elementId, _jsPlumb) {
8517             return (anchorParams) ? _jsPlumb.makeAnchor(anchorParams, elementId, _jsPlumb) : null;
8518         },
8519         _updateConnectedClass = function (conn, element, _jsPlumb, remove) {
8520             if (element != null) {
8521                 element._jsPlumbConnections = element._jsPlumbConnections || {};
8522                 if (remove) {
8523                     delete element._jsPlumbConnections[conn.id];
8524                 }
8525                 else {
8526                     element._jsPlumbConnections[conn.id] = true;
8527                 }
8528 
8529                 if (_ju.isEmpty(element._jsPlumbConnections)) {
8530                     _jsPlumb.removeClass(element, _jsPlumb.connectedClass);
8531                 }
8532                 else {
8533                     _jsPlumb.addClass(element, _jsPlumb.connectedClass);
8534                 }
8535             }
8536         };
8537 
8538     _jp.Connection = function (params) {
8539         var _newEndpoint = params.newEndpoint;
8540 
8541         this.id = params.id;
8542         this.connector = null;
8543         this.idPrefix = "_jsplumb_c_";
8544         this.defaultLabelLocation = 0.5;
8545         this.defaultOverlayKeys = ["Overlays", "ConnectionOverlays"];
8546         // if a new connection is the result of moving some existing connection, params.previousConnection
8547         // will have that Connection in it. listeners for the jsPlumbConnection event can look for that
8548         // member and take action if they need to.
8549         this.previousConnection = params.previousConnection;
8550         this.source = _jp.getElement(params.source);
8551         this.target = _jp.getElement(params.target);
8552         // sourceEndpoint and targetEndpoint override source/target, if they are present. but 
8553         // source is not overridden if the Endpoint has declared it is not the final target of a connection;
8554         // instead we use the source that the Endpoint declares will be the final source element.
8555         if (params.sourceEndpoint) {
8556             this.source = params.sourceEndpoint.getElement();
8557         }
8558         if (params.targetEndpoint) {
8559             this.target = params.targetEndpoint.getElement();
8560         }
8561 
8562         _jp.OverlayCapableJsPlumbUIComponent.apply(this, arguments);
8563 
8564         this.sourceId = this._jsPlumb.instance.getId(this.source);
8565         this.targetId = this._jsPlumb.instance.getId(this.target);
8566         this.scope = params.scope; // scope may have been passed in to the connect call. if it wasn't, we will pull it from the source endpoint, after having initialised the endpoints.            
8567         this.endpoints = [];
8568         this.endpointStyles = [];
8569 
8570         var _jsPlumb = this._jsPlumb.instance;
8571 
8572         _jsPlumb.manage(this.sourceId, this.source);
8573         _jsPlumb.manage(this.targetId, this.target);
8574 
8575         this._jsPlumb.visible = true;
8576         this._jsPlumb.editable = params.editable === true;
8577         this._jsPlumb.params = {
8578             cssClass: params.cssClass,
8579             container: params.container,
8580             "pointer-events": params["pointer-events"],
8581             editorParams: params.editorParams,
8582             overlays: params.overlays
8583         };
8584         this._jsPlumb.lastPaintedAt = null;
8585 
8586         // listen to mouseover and mouseout events passed from the container delegate.
8587         this.bind("mouseover", function () {
8588             this.setHover(true);
8589         }.bind(this));
8590         this.bind("mouseout", function () {
8591             this.setHover(false);
8592         }.bind(this));
8593 
8594         this.editableRequested = params.editable !== false;
8595         this.setEditable = function(e) {
8596             return this.connector ? this.connector.setEditable(e) : false;
8597         };
8598         this.isEditable = function() { return this.connector ? this.connector.isEditable() : false; };
8599         this.isEditing = function() { return this.connector ? this.connector.isEditing() : false; };
8600 
8601 // INITIALISATION CODE
8602 
8603         this.makeEndpoint = function (isSource, el, elId, ep) {
8604             elId = elId || this._jsPlumb.instance.getId(el);
8605             return this.prepareEndpoint(_jsPlumb, _newEndpoint, this, ep, isSource ? 0 : 1, params, el, elId);
8606         };
8607 
8608         // if type given, get the endpoint definitions mapping to that type from the jsplumb instance, and use those.
8609         // we apply types at the end of this constructor but endpoints are only honoured in a type definition at
8610         // create time.
8611         if (params.type) {
8612             params.endpoints = params.endpoints || this._jsPlumb.instance.deriveEndpointAndAnchorSpec(params.type).endpoints;
8613         }
8614 
8615         var eS = this.makeEndpoint(true, this.source, this.sourceId, params.sourceEndpoint),
8616             eT = this.makeEndpoint(false, this.target, this.targetId, params.targetEndpoint);
8617 
8618         if (eS) {
8619             _ju.addToList(params.endpointsByElement, this.sourceId, eS);
8620         }
8621         if (eT) {
8622             _ju.addToList(params.endpointsByElement, this.targetId, eT);
8623         }
8624         // if scope not set, set it to be the scope for the source endpoint.
8625         if (!this.scope) {
8626             this.scope = this.endpoints[0].scope;
8627         }
8628 
8629         // if explicitly told to (or not to) delete endpoints when empty, override endpoint's preferences
8630         if (params.deleteEndpointsOnEmpty != null) {
8631             this.endpoints[0].setDeleteOnEmpty(params.deleteEndpointsOnEmpty);
8632             this.endpoints[1].setDeleteOnEmpty(params.deleteEndpointsOnEmpty);
8633         }
8634 //        else {
8635 //            // otherwise, unless the endpoints say otherwise, mark them for deletion.
8636 //            if (!this.endpoints[0]._doNotDeleteOnDetach) this.endpoints[0]._deleteOnDetach = true;
8637 //            if (!this.endpoints[1]._doNotDeleteOnDetach) this.endpoints[1]._deleteOnDetach = true;
8638 //        }
8639 
8640 // -------------------------- DEFAULT TYPE ---------------------------------------------
8641 
8642         // DETACHABLE
8643         var _detachable = _jsPlumb.Defaults.ConnectionsDetachable;
8644         if (params.detachable === false) {
8645             _detachable = false;
8646         }
8647         if (this.endpoints[0].connectionsDetachable === false) {
8648             _detachable = false;
8649         }
8650         if (this.endpoints[1].connectionsDetachable === false) {
8651             _detachable = false;
8652         }
8653         // REATTACH
8654         var _reattach = params.reattach || this.endpoints[0].reattachConnections || this.endpoints[1].reattachConnections || _jsPlumb.Defaults.ReattachConnections;
8655 
8656         this.appendToDefaultType({
8657             detachable: _detachable,
8658             reattach: _reattach,
8659             paintStyle:this.endpoints[0].connectorStyle || this.endpoints[1].connectorStyle || params.paintStyle || _jsPlumb.Defaults.PaintStyle || _jp.Defaults.PaintStyle,
8660             hoverPaintStyle:this.endpoints[0].connectorHoverStyle || this.endpoints[1].connectorHoverStyle || params.hoverPaintStyle || _jsPlumb.Defaults.HoverPaintStyle || _jp.Defaults.HoverPaintStyle
8661         });
8662 
8663         var _suspendedAt = _jsPlumb.getSuspendedAt();
8664         if (!_jsPlumb.isSuspendDrawing()) {
8665             // paint the endpoints
8666             var myInfo = _jsPlumb.getCachedData(this.sourceId),
8667                 myOffset = myInfo.o, myWH = myInfo.s,
8668                 otherInfo = _jsPlumb.getCachedData(this.targetId),
8669                 otherOffset = otherInfo.o,
8670                 otherWH = otherInfo.s,
8671                 initialTimestamp = _suspendedAt || _jsPlumb.timestamp(),
8672                 anchorLoc = this.endpoints[0].anchor.compute({
8673                     xy: [ myOffset.left, myOffset.top ], wh: myWH, element: this.endpoints[0],
8674                     elementId: this.endpoints[0].elementId,
8675                     txy: [ otherOffset.left, otherOffset.top ], twh: otherWH, tElement: this.endpoints[1],
8676                     timestamp: initialTimestamp
8677                 });
8678 
8679             this.endpoints[0].paint({ anchorLoc: anchorLoc, timestamp: initialTimestamp });
8680 
8681             anchorLoc = this.endpoints[1].anchor.compute({
8682                 xy: [ otherOffset.left, otherOffset.top ], wh: otherWH, element: this.endpoints[1],
8683                 elementId: this.endpoints[1].elementId,
8684                 txy: [ myOffset.left, myOffset.top ], twh: myWH, tElement: this.endpoints[0],
8685                 timestamp: initialTimestamp
8686             });
8687             this.endpoints[1].paint({ anchorLoc: anchorLoc, timestamp: initialTimestamp });
8688         }
8689 
8690         this.getTypeDescriptor = function () {
8691             return "connection";
8692         };
8693         this.getAttachedElements = function () {
8694             return this.endpoints;
8695         };
8696 
8697         this.isDetachable = function () {
8698             return this._jsPlumb.detachable === true;
8699         };
8700         this.setDetachable = function (detachable) {
8701             this._jsPlumb.detachable = detachable === true;
8702         };
8703         this.isReattach = function () {
8704             return this._jsPlumb.reattach === true || this.endpoints[0].reattachConnections === true || this.endpoints[1].reattachConnections === true;
8705         };
8706         this.setReattach = function (reattach) {
8707             this._jsPlumb.reattach = reattach === true;
8708         };
8709 
8710 //        this["delete"] = function() {
8711 //            this.endpoints[0].detachFromConnection(this);
8712 //            this.endpoints[1].detachFromConnection(this);
8713 //            params.deleteConnection(this);
8714 //        };
8715 
8716 // END INITIALISATION CODE
8717 
8718 
8719 // COST + DIRECTIONALITY
8720         // if cost not supplied, try to inherit from source endpoint
8721         this._jsPlumb.cost = params.cost || this.endpoints[0].getConnectionCost();
8722         this._jsPlumb.directed = params.directed;
8723         // inherit directed flag if set no source endpoint
8724         if (params.directed == null) {
8725             this._jsPlumb.directed = this.endpoints[0].areConnectionsDirected();
8726         }
8727 // END COST + DIRECTIONALITY
8728 
8729 // PARAMETERS
8730         // merge all the parameters objects into the connection.  parameters set
8731         // on the connection take precedence; then source endpoint params, then
8732         // finally target endpoint params.
8733         var _p = _jp.extend({}, this.endpoints[1].getParameters());
8734         _jp.extend(_p, this.endpoints[0].getParameters());
8735         _jp.extend(_p, this.getParameters());
8736         this.setParameters(_p);
8737 // END PARAMETERS
8738 
8739 // PAINTING
8740 
8741         this.setConnector(this.endpoints[0].connector || this.endpoints[1].connector || params.connector || _jsPlumb.Defaults.Connector || _jp.Defaults.Connector, true);
8742         if (params.geometry) {
8743             this.connector.setGeometry(params.geometry);
8744         }
8745         var data = params.data == null || !_ju.isObject(params.data) ? {} : params.data;
8746         this.getData = function() { return data; };
8747         this.setData = function(d) { data = d || {}; };
8748         this.mergeData = function(d) { data = _jp.extend(data, d); };
8749 
8750         // the very last thing we do is apply types, if there are any.
8751         var _types = [ "default", this.endpoints[0].connectionType, this.endpoints[1].connectionType,  params.type ].join(" ");
8752         if (/[^\s]/.test(_types)) {
8753             this.addType(_types, params.data, true);
8754         }
8755 
8756         this.updateConnectedClass();
8757 
8758 // END PAINTING    
8759     };
8760 
8761     _ju.extend(_jp.Connection, _jp.OverlayCapableJsPlumbUIComponent, {
8762         applyType: function (t, doNotRepaint, typeMap) {
8763 
8764             // none of these things result in the creation of objects so can be ignored.
8765             if (t.detachable != null) {
8766                 this.setDetachable(t.detachable);
8767             }
8768             if (t.reattach != null) {
8769                 this.setReattach(t.reattach);
8770             }
8771             if (t.scope) {
8772                 this.scope = t.scope;
8773             }
8774 
8775             if (t.cssClass != null && this.canvas) {
8776                 this._jsPlumb.instance.addClass(this.canvas, t.cssClass);
8777             }
8778 
8779             var _anchors = null;
8780             // this also results in the creation of objects.
8781             if (t.anchor) {
8782                 // note that even if the param was anchor, we store `anchors`.
8783                 _anchors = this.getCachedTypeItem("anchors", typeMap.anchor);
8784                 if (_anchors == null) {
8785                     _anchors = [ this._jsPlumb.instance.makeAnchor(t.anchor), this._jsPlumb.instance.makeAnchor(t.anchor) ];
8786                     this.cacheTypeItem("anchors", _anchors, typeMap.anchor);
8787                 }
8788             }
8789             else if (t.anchors) {
8790                 _anchors = this.getCachedTypeItem("anchors", typeMap.anchors);
8791                 if (_anchors == null) {
8792                     _anchors = [
8793                         this._jsPlumb.instance.makeAnchor(t.anchors[0]),
8794                         this._jsPlumb.instance.makeAnchor(t.anchors[1])
8795                     ];
8796                     this.cacheTypeItem("anchors", _anchors, typeMap.anchors);
8797                 }
8798             }
8799             if (_anchors != null) {
8800                 this.endpoints[0].anchor = _anchors[0];
8801                 this.endpoints[1].anchor = _anchors[1];
8802                 if (this.endpoints[1].anchor.isDynamic) {
8803                     this._jsPlumb.instance.repaint(this.endpoints[1].elementId);
8804                 }
8805             }
8806 
8807             _jp.OverlayCapableJsPlumbUIComponent.applyType(this, t);
8808         },
8809         addClass: function (c, informEndpoints) {
8810             if (informEndpoints) {
8811                 this.endpoints[0].addClass(c);
8812                 this.endpoints[1].addClass(c);
8813                 if (this.suspendedEndpoint) {
8814                     this.suspendedEndpoint.addClass(c);
8815                 }
8816             }
8817             if (this.connector) {
8818                 this.connector.addClass(c);
8819             }
8820         },
8821         removeClass: function (c, informEndpoints) {
8822             if (informEndpoints) {
8823                 this.endpoints[0].removeClass(c);
8824                 this.endpoints[1].removeClass(c);
8825                 if (this.suspendedEndpoint) {
8826                     this.suspendedEndpoint.removeClass(c);
8827                 }
8828             }
8829             if (this.connector) {
8830                 this.connector.removeClass(c);
8831             }
8832         },
8833         isVisible: function () {
8834             return this._jsPlumb.visible;
8835         },
8836         setVisible: function (v) {
8837             this._jsPlumb.visible = v;
8838             if (this.connector) {
8839                 this.connector.setVisible(v);
8840             }
8841             this.repaint();
8842         },
8843         cleanup: function () {
8844             this.updateConnectedClass(true);
8845             this.endpoints = null;
8846             this.source = null;
8847             this.target = null;
8848             if (this.connector != null) {
8849                 this.connector.cleanup(true);
8850                 this.connector.destroy(true);
8851             }
8852             this.connector = null;
8853         },
8854         updateConnectedClass:function(remove) {
8855             if (this._jsPlumb) {
8856                 _updateConnectedClass(this, this.source, this._jsPlumb.instance, remove);
8857                 _updateConnectedClass(this, this.target, this._jsPlumb.instance, remove);
8858             }
8859         },
8860         setHover: function (state) {
8861             if (this.connector && this._jsPlumb && !this._jsPlumb.instance.isConnectionBeingDragged()) {
8862                 this.connector.setHover(state);
8863                 root.jsPlumb[state ? "addClass" : "removeClass"](this.source, this._jsPlumb.instance.hoverSourceClass);
8864                 root.jsPlumb[state ? "addClass" : "removeClass"](this.target, this._jsPlumb.instance.hoverTargetClass);
8865             }
8866         },
8867         getUuids:function() {
8868             return [ this.endpoints[0].getUuid(), this.endpoints[1].getUuid() ];
8869         },
8870         getCost: function () {
8871             return this._jsPlumb ? this._jsPlumb.cost : -Infinity;
8872         },
8873         setCost: function (c) {
8874             this._jsPlumb.cost = c;
8875         },
8876         isDirected: function () {
8877             return this._jsPlumb.directed === true;
8878         },
8879         getConnector: function () {
8880             return this.connector;
8881         },
8882         getGeometry : function() {
8883             return this.connector ? this.connector.getGeometry() : null;
8884         },
8885         setGeometry : function(g) {
8886             if (this.connector) {
8887                 this.connector.setGeometry(g);
8888             }
8889         },
8890         prepareConnector:function(connectorSpec, typeId) {
8891             var connectorArgs = {
8892                     _jsPlumb: this._jsPlumb.instance,
8893                     cssClass: (this._jsPlumb.params.cssClass || "") + (this.isEditable() ? this._jsPlumb.instance.editableConnectorClass : ""),
8894                     container: this._jsPlumb.params.container,
8895                     "pointer-events": this._jsPlumb.params["pointer-events"],
8896                     editable:this.editableRequested
8897                 },
8898                 renderMode = this._jsPlumb.instance.getRenderMode(),
8899                 connector;
8900 
8901             if (_ju.isString(connectorSpec)) {
8902                 connector = makeConnector(this._jsPlumb.instance, renderMode, connectorSpec, connectorArgs, this);
8903             } // lets you use a string as shorthand.
8904             else if (_ju.isArray(connectorSpec)) {
8905                 if (connectorSpec.length === 1) {
8906                     connector = makeConnector(this._jsPlumb.instance, renderMode, connectorSpec[0], connectorArgs, this);
8907                 }
8908                 else {
8909                     connector = makeConnector(this._jsPlumb.instance, renderMode, connectorSpec[0], _ju.merge(connectorSpec[1], connectorArgs), this);
8910                 }
8911             }
8912             if (typeId != null) {
8913                 connector.typeId = typeId;
8914             }
8915             return connector;
8916         },
8917         setPreparedConnector: function(connector, doNotRepaint, doNotChangeListenerComponent, typeId) {
8918 
8919             var previous, previousClasses = "";
8920             // the connector will not be cleaned up if it was set as part of a type, because `typeId` will be set on it
8921             // and we havent passed in `true` for "force" here.
8922             if (this.connector != null) {
8923                 previous = this.connector;
8924                 previousClasses = previous.getClass();
8925                 this.connector.cleanup();
8926                 this.connector.destroy();
8927             }
8928 
8929             this.connector = connector;
8930             if (typeId) {
8931                 this.cacheTypeItem("connector", connector, typeId);
8932             }
8933 
8934             this.canvas = this.connector.canvas;
8935             this.bgCanvas = this.connector.bgCanvas;
8936 
8937             // put classes from prior connector onto the canvas
8938             this.addClass(previousClasses);
8939 
8940             // new: instead of binding listeners per connector, we now just have one delegate on the container.
8941             // so for that handler we set the connection as the '_jsPlumb' member of the canvas element, and
8942             // bgCanvas, if it exists, which it does right now in the VML renderer, so it won't from v 2.0.0 onwards.
8943             if (this.canvas) {
8944                 this.canvas._jsPlumb = this;
8945             }
8946             if (this.bgCanvas) {
8947                 this.bgCanvas._jsPlumb = this;
8948             }
8949 
8950             if (previous != null) {
8951                 var o = this.getOverlays();
8952                 for (var i = 0; i < o.length; i++) {
8953                     if (o[i].transfer) {
8954                         o[i].transfer(this.connector);
8955                     }
8956                 }
8957             }
8958 
8959             if (!doNotChangeListenerComponent) {
8960                 this.setListenerComponent(this.connector);
8961             }
8962             if (!doNotRepaint) {
8963                 this.repaint();
8964             }
8965         },
8966         setConnector: function (connectorSpec, doNotRepaint, doNotChangeListenerComponent, typeId) {
8967             var connector = this.prepareConnector(connectorSpec, typeId);
8968             this.setPreparedConnector(connector, doNotRepaint, doNotChangeListenerComponent, typeId);
8969         },
8970         paint: function (params) {
8971 
8972             if (!this._jsPlumb.instance.isSuspendDrawing() && this._jsPlumb.visible) {
8973                 params = params || {};
8974                 var timestamp = params.timestamp,
8975                 // if the moving object is not the source we must transpose the two references.
8976                     swap = false,
8977                     tId = swap ? this.sourceId : this.targetId, sId = swap ? this.targetId : this.sourceId,
8978                     tIdx = swap ? 0 : 1, sIdx = swap ? 1 : 0;
8979 
8980                 if (timestamp == null || timestamp !== this._jsPlumb.lastPaintedAt) {
8981                     var sourceInfo = this._jsPlumb.instance.updateOffset({elId:sId}).o,
8982                         targetInfo = this._jsPlumb.instance.updateOffset({elId:tId}).o,
8983                         sE = this.endpoints[sIdx], tE = this.endpoints[tIdx];
8984 
8985                     var sAnchorP = sE.anchor.getCurrentLocation({xy: [sourceInfo.left, sourceInfo.top], wh: [sourceInfo.width, sourceInfo.height], element: sE, timestamp: timestamp}),
8986                         tAnchorP = tE.anchor.getCurrentLocation({xy: [targetInfo.left, targetInfo.top], wh: [targetInfo.width, targetInfo.height], element: tE, timestamp: timestamp});
8987 
8988                     this.connector.resetBounds();
8989 
8990                     this.connector.compute({
8991                         sourcePos: sAnchorP,
8992                         targetPos: tAnchorP,
8993                         sourceEndpoint: this.endpoints[sIdx],
8994                         targetEndpoint: this.endpoints[tIdx],
8995                         "stroke-width": this._jsPlumb.paintStyleInUse.strokeWidth,
8996                         sourceInfo: sourceInfo,
8997                         targetInfo: targetInfo
8998                     });
8999 
9000                     var overlayExtents = { minX: Infinity, minY: Infinity, maxX: -Infinity, maxY: -Infinity };
9001 
9002                     // compute overlays. we do this first so we can get their placements, and adjust the
9003                     // container if needs be (if an overlay would be clipped)
9004                     for (var i in this._jsPlumb.overlays) {
9005                         if (this._jsPlumb.overlays.hasOwnProperty(i)) {
9006                             var o = this._jsPlumb.overlays[i];
9007                             if (o.isVisible()) {
9008                                 this._jsPlumb.overlayPlacements[i] = o.draw(this.connector, this._jsPlumb.paintStyleInUse, this.getAbsoluteOverlayPosition(o));
9009                                 overlayExtents.minX = Math.min(overlayExtents.minX, this._jsPlumb.overlayPlacements[i].minX);
9010                                 overlayExtents.maxX = Math.max(overlayExtents.maxX, this._jsPlumb.overlayPlacements[i].maxX);
9011                                 overlayExtents.minY = Math.min(overlayExtents.minY, this._jsPlumb.overlayPlacements[i].minY);
9012                                 overlayExtents.maxY = Math.max(overlayExtents.maxY, this._jsPlumb.overlayPlacements[i].maxY);
9013                             }
9014                         }
9015                     }
9016 
9017                     var lineWidth = parseFloat(this._jsPlumb.paintStyleInUse.strokeWidth || 1) / 2,
9018                         outlineWidth = parseFloat(this._jsPlumb.paintStyleInUse.strokeWidth || 0),
9019                         extents = {
9020                             xmin: Math.min(this.connector.bounds.minX - (lineWidth + outlineWidth), overlayExtents.minX),
9021                             ymin: Math.min(this.connector.bounds.minY - (lineWidth + outlineWidth), overlayExtents.minY),
9022                             xmax: Math.max(this.connector.bounds.maxX + (lineWidth + outlineWidth), overlayExtents.maxX),
9023                             ymax: Math.max(this.connector.bounds.maxY + (lineWidth + outlineWidth), overlayExtents.maxY)
9024                         };
9025                     // paint the connector.
9026                     this.connector.paint(this._jsPlumb.paintStyleInUse, null, extents);
9027                     // and then the overlays
9028                     for (var j in this._jsPlumb.overlays) {
9029                         if (this._jsPlumb.overlays.hasOwnProperty(j)) {
9030                             var p = this._jsPlumb.overlays[j];
9031                             if (p.isVisible()) {
9032                                 p.paint(this._jsPlumb.overlayPlacements[j], extents);
9033                             }
9034                         }
9035                     }
9036                 }
9037                 this._jsPlumb.lastPaintedAt = timestamp;
9038             }
9039         },
9040         repaint: function (params) {
9041             params = params || {};
9042             this.paint({ elId: this.sourceId, recalc: !(params.recalc === false), timestamp: params.timestamp});
9043         },
9044         prepareEndpoint: function (_jsPlumb, _newEndpoint, conn, existing, index, params, element, elementId) {
9045             var e;
9046             if (existing) {
9047                 conn.endpoints[index] = existing;
9048                 existing.addConnection(conn);
9049             } else {
9050                 if (!params.endpoints) {
9051                     params.endpoints = [ null, null ];
9052                 }
9053                 var ep = params.endpoints[index] || params.endpoint || _jsPlumb.Defaults.Endpoints[index] || _jp.Defaults.Endpoints[index] || _jsPlumb.Defaults.Endpoint || _jp.Defaults.Endpoint;
9054                 if (!params.endpointStyles) {
9055                     params.endpointStyles = [ null, null ];
9056                 }
9057                 if (!params.endpointHoverStyles) {
9058                     params.endpointHoverStyles = [ null, null ];
9059                 }
9060                 var es = params.endpointStyles[index] || params.endpointStyle || _jsPlumb.Defaults.EndpointStyles[index] || _jp.Defaults.EndpointStyles[index] || _jsPlumb.Defaults.EndpointStyle || _jp.Defaults.EndpointStyle;
9061                 // Endpoints derive their fill from the connector's stroke, if no fill was specified.
9062                 if (es.fill == null && params.paintStyle != null) {
9063                     es.fill = params.paintStyle.stroke;
9064                 }
9065 
9066                 if (es.outlineStroke == null && params.paintStyle != null) {
9067                     es.outlineStroke = params.paintStyle.outlineStroke;
9068                 }
9069                 if (es.outlineWidth == null && params.paintStyle != null) {
9070                     es.outlineWidth = params.paintStyle.outlineWidth;
9071                 }
9072 
9073                 var ehs = params.endpointHoverStyles[index] || params.endpointHoverStyle || _jsPlumb.Defaults.EndpointHoverStyles[index] || _jp.Defaults.EndpointHoverStyles[index] || _jsPlumb.Defaults.EndpointHoverStyle || _jp.Defaults.EndpointHoverStyle;
9074                 // endpoint hover fill style is derived from connector's hover stroke style
9075                 if (params.hoverPaintStyle != null) {
9076                     if (ehs == null) {
9077                         ehs = {};
9078                     }
9079                     if (ehs.fill == null) {
9080                         ehs.fill = params.hoverPaintStyle.stroke;
9081                     }
9082                 }
9083                 var a = params.anchors ? params.anchors[index] :
9084                         params.anchor ? params.anchor :
9085                             _makeAnchor(_jsPlumb.Defaults.Anchors[index], elementId, _jsPlumb) ||
9086                             _makeAnchor(_jp.Defaults.Anchors[index], elementId, _jsPlumb) ||
9087                             _makeAnchor(_jsPlumb.Defaults.Anchor, elementId, _jsPlumb) ||
9088                             _makeAnchor(_jp.Defaults.Anchor, elementId, _jsPlumb),
9089                     u = params.uuids ? params.uuids[index] : null;
9090 
9091                 e = _newEndpoint({
9092                     paintStyle: es, hoverPaintStyle: ehs, endpoint: ep, connections: [ conn ],
9093                     uuid: u, anchor: a, source: element, scope: params.scope,
9094                     reattach: params.reattach || _jsPlumb.Defaults.ReattachConnections,
9095                     detachable: params.detachable || _jsPlumb.Defaults.ConnectionsDetachable
9096                 });
9097                 if (existing == null) {
9098                     e.setDeleteOnEmpty(true);
9099                 }
9100                 conn.endpoints[index] = e;
9101 
9102                 if (params.drawEndpoints === false) {
9103                     e.setVisible(false, true, true);
9104                 }
9105 
9106             }
9107             return e;
9108         }
9109 
9110     }); // END Connection class            
9111 }).call(typeof window !== 'undefined' ? window : this);
9112 
9113 /*
9114  * jsPlumb Community Edition
9115  * 
9116  * Provides a way to visually connect elements on an HTML page, using SVG.
9117  * 
9118  * This file contains the code for creating and manipulating anchors.
9119  *
9120  * Copyright (c) 2010 - 2017 jsPlumb (hello@jsplumbtoolkit.com)
9121  * 
9122  * https://jsplumbtoolkit.com
9123  * https://github.com/jsplumb/jsplumb
9124  * 
9125  * Dual licensed under the MIT and GPL2 licenses.
9126  */
9127 ;
9128 (function () {
9129 
9130     "use strict";
9131 
9132     var root = this,
9133         _ju = root.jsPlumbUtil,
9134         _jp = root.jsPlumb;
9135 
9136     //
9137     // manages anchors for all elements.
9138     //
9139     _jp.AnchorManager = function (params) {
9140         var _amEndpoints = {},
9141             continuousAnchorLocations = {},
9142             userDefinedContinuousAnchorLocations = {},
9143             continuousAnchorOrientations = {},
9144             Orientation = { HORIZONTAL: "horizontal", VERTICAL: "vertical", DIAGONAL: "diagonal", IDENTITY: "identity" },
9145             axes = ["left", "top", "right", "bottom"],
9146             connectionsByElementId = {},
9147             self = this,
9148             anchorLists = {},
9149             jsPlumbInstance = params.jsPlumbInstance,
9150             floatingConnections = {},
9151             calculateOrientation = function (sourceId, targetId, sd, td, sourceAnchor, targetAnchor) {
9152 
9153                 if (sourceId === targetId) {
9154                     return {
9155                         orientation: Orientation.IDENTITY,
9156                         a: ["top", "top"]
9157                     };
9158                 }
9159 
9160                 var theta = Math.atan2((td.centery - sd.centery), (td.centerx - sd.centerx)),
9161                     theta2 = Math.atan2((sd.centery - td.centery), (sd.centerx - td.centerx));
9162 
9163 // --------------------------------------------------------------------------------------
9164 
9165                 // improved face calculation. get midpoints of each face for source and target, then put in an array with all combinations of
9166                 // source/target faces. sort this array by distance between midpoints. the entry at index 0 is our preferred option. we can
9167                 // go through the array one by one until we find an entry in which each requested face is supported.
9168                 var candidates = [], midpoints = { };
9169                 (function (types, dim) {
9170                     for (var i = 0; i < types.length; i++) {
9171                         midpoints[types[i]] = {
9172                             "left": [ dim[i].left, dim[i].centery ],
9173                             "right": [ dim[i].right, dim[i].centery ],
9174                             "top": [ dim[i].centerx, dim[i].top ],
9175                             "bottom": [ dim[i].centerx , dim[i].bottom]
9176                         };
9177                     }
9178                 })([ "source", "target" ], [ sd, td ]);
9179 
9180                 for (var sf = 0; sf < axes.length; sf++) {
9181                     for (var tf = 0; tf < axes.length; tf++) {
9182                         candidates.push({
9183                             source: axes[sf],
9184                             target: axes[tf],
9185                             dist: Biltong.lineLength(midpoints.source[axes[sf]], midpoints.target[axes[tf]])
9186                         });
9187                     }
9188                 }
9189 
9190                 candidates.sort(function (a, b) {
9191                     return a.dist < b.dist ? -1 : a.dist > b.dist ? 1 : 0;
9192                 });
9193 
9194                 // now go through this list and try to get an entry that satisfies both (there will be one, unless one of the anchors
9195                 // declares no available faces)
9196                 var sourceEdge = candidates[0].source, targetEdge = candidates[0].target;
9197                 for (var i = 0; i < candidates.length; i++) {
9198 
9199                     if (!sourceAnchor.isContinuous || sourceAnchor.isEdgeSupported(candidates[i].source)) {
9200                         sourceEdge = candidates[i].source;
9201                     }
9202                     else {
9203                         sourceEdge = null;
9204                     }
9205 
9206                     if (!targetAnchor.isContinuous || targetAnchor.isEdgeSupported(candidates[i].target)) {
9207                         targetEdge = candidates[i].target;
9208                     }
9209                     else {
9210                         targetEdge = null;
9211                     }
9212 
9213                     if (sourceEdge != null && targetEdge != null) {
9214                         break;
9215                     }
9216                 }
9217 
9218 // --------------------------------------------------------------------------------------
9219 
9220                 return {
9221                     a: [ sourceEdge, targetEdge ],
9222                     theta: theta,
9223                     theta2: theta2
9224                 };
9225             },
9226         // used by placeAnchors function
9227             placeAnchorsOnLine = function (desc, elementDimensions, elementPosition, connections, horizontal, otherMultiplier, reverse) {
9228                 var a = [], step = elementDimensions[horizontal ? 0 : 1] / (connections.length + 1);
9229 
9230                 for (var i = 0; i < connections.length; i++) {
9231                     var val = (i + 1) * step, other = otherMultiplier * elementDimensions[horizontal ? 1 : 0];
9232                     if (reverse) {
9233                         val = elementDimensions[horizontal ? 0 : 1] - val;
9234                     }
9235 
9236                     var dx = (horizontal ? val : other), x = elementPosition[0] + dx, xp = dx / elementDimensions[0],
9237                         dy = (horizontal ? other : val), y = elementPosition[1] + dy, yp = dy / elementDimensions[1];
9238 
9239                     a.push([ x, y, xp, yp, connections[i][1], connections[i][2] ]);
9240                 }
9241 
9242                 return a;
9243             },
9244         // used by edgeSortFunctions
9245             currySort = function (reverseAngles) {
9246                 return function (a, b) {
9247                     var r = true;
9248                     if (reverseAngles) {
9249                         r = a[0][0] < b[0][0];
9250                     }
9251                     else {
9252                         r = a[0][0] > b[0][0];
9253                     }
9254                     return r === false ? -1 : 1;
9255                 };
9256             },
9257         // used by edgeSortFunctions
9258             leftSort = function (a, b) {
9259                 // first get adjusted values
9260                 var p1 = a[0][0] < 0 ? -Math.PI - a[0][0] : Math.PI - a[0][0],
9261                     p2 = b[0][0] < 0 ? -Math.PI - b[0][0] : Math.PI - b[0][0];
9262                 if (p1 > p2) {
9263                     return 1;
9264                 }
9265                 else {
9266                     return -1;
9267                 }
9268             },
9269         // used by placeAnchors
9270             edgeSortFunctions = {
9271                 "top": function (a, b) {
9272                     return a[0] > b[0] ? 1 : -1;
9273                 },
9274                 "right": currySort(true),
9275                 "bottom": currySort(true),
9276                 "left": leftSort
9277             },
9278         // used by placeAnchors
9279             _sortHelper = function (_array, _fn) {
9280                 return _array.sort(_fn);
9281             },
9282         // used by AnchorManager.redraw
9283             placeAnchors = function (elementId, _anchorLists) {
9284                 var cd = jsPlumbInstance.getCachedData(elementId), sS = cd.s, sO = cd.o,
9285                     placeSomeAnchors = function (desc, elementDimensions, elementPosition, unsortedConnections, isHorizontal, otherMultiplier, orientation) {
9286                         if (unsortedConnections.length > 0) {
9287                             var sc = _sortHelper(unsortedConnections, edgeSortFunctions[desc]), // puts them in order based on the target element's pos on screen
9288                                 reverse = desc === "right" || desc === "top",
9289                                 anchors = placeAnchorsOnLine(desc, elementDimensions,
9290                                     elementPosition, sc,
9291                                     isHorizontal, otherMultiplier, reverse);
9292 
9293                             // takes a computed anchor position and adjusts it for parent offset and scroll, then stores it.
9294                             var _setAnchorLocation = function (endpoint, anchorPos) {
9295                                 continuousAnchorLocations[endpoint.id] = [ anchorPos[0], anchorPos[1], anchorPos[2], anchorPos[3] ];
9296                                 continuousAnchorOrientations[endpoint.id] = orientation;
9297                             };
9298 
9299                             for (var i = 0; i < anchors.length; i++) {
9300                                 var c = anchors[i][4], weAreSource = c.endpoints[0].elementId === elementId, weAreTarget = c.endpoints[1].elementId === elementId;
9301                                 if (weAreSource) {
9302                                     _setAnchorLocation(c.endpoints[0], anchors[i]);
9303                                 }
9304                                 if (weAreTarget) {
9305                                     _setAnchorLocation(c.endpoints[1], anchors[i]);
9306                                 }
9307                             }
9308                         }
9309                     };
9310 
9311                 placeSomeAnchors("bottom", sS, [sO.left, sO.top], _anchorLists.bottom, true, 1, [0, 1]);
9312                 placeSomeAnchors("top", sS, [sO.left, sO.top], _anchorLists.top, true, 0, [0, -1]);
9313                 placeSomeAnchors("left", sS, [sO.left, sO.top], _anchorLists.left, false, 0, [-1, 0]);
9314                 placeSomeAnchors("right", sS, [sO.left, sO.top], _anchorLists.right, false, 1, [1, 0]);
9315             };
9316 
9317         this.reset = function () {
9318             _amEndpoints = {};
9319             connectionsByElementId = {};
9320             anchorLists = {};
9321         };
9322         this.addFloatingConnection = function (key, conn) {
9323             floatingConnections[key] = conn;
9324         };
9325         this.removeFloatingConnection = function (key) {
9326             delete floatingConnections[key];
9327         };
9328         this.newConnection = function (conn) {
9329             var sourceId = conn.sourceId, targetId = conn.targetId,
9330                 ep = conn.endpoints,
9331                 doRegisterTarget = true,
9332                 registerConnection = function (otherIndex, otherEndpoint, otherAnchor, elId, c) {
9333                     if ((sourceId === targetId) && otherAnchor.isContinuous) {
9334                         // remove the target endpoint's canvas.  we dont need it.
9335                         conn._jsPlumb.instance.removeElement(ep[1].canvas);
9336                         doRegisterTarget = false;
9337                     }
9338                     _ju.addToList(connectionsByElementId, elId, [c, otherEndpoint, otherAnchor.constructor === _jp.DynamicAnchor]);
9339                 };
9340 
9341             registerConnection(0, ep[0], ep[0].anchor, targetId, conn);
9342             if (doRegisterTarget) {
9343                 registerConnection(1, ep[1], ep[1].anchor, sourceId, conn);
9344             }
9345         };
9346         var removeEndpointFromAnchorLists = function (endpoint) {
9347             (function (list, eId) {
9348                 if (list) {  // transient anchors dont get entries in this list.
9349                     var f = function (e) {
9350                         return e[4] === eId;
9351                     };
9352                     _ju.removeWithFunction(list.top, f);
9353                     _ju.removeWithFunction(list.left, f);
9354                     _ju.removeWithFunction(list.bottom, f);
9355                     _ju.removeWithFunction(list.right, f);
9356                 }
9357             })(anchorLists[endpoint.elementId], endpoint.id);
9358         };
9359         this.connectionDetached = function (connInfo, doNotRedraw) {
9360             var connection = connInfo.connection || connInfo,
9361                 sourceId = connInfo.sourceId,
9362                 targetId = connInfo.targetId,
9363                 ep = connection.endpoints,
9364                 removeConnection = function (otherIndex, otherEndpoint, otherAnchor, elId, c) {
9365                    _ju.removeWithFunction(connectionsByElementId[elId], function (_c) {
9366                         return _c[0].id === c.id;
9367                     });
9368                 };
9369 
9370             removeConnection(1, ep[1], ep[1].anchor, sourceId, connection);
9371             removeConnection(0, ep[0], ep[0].anchor, targetId, connection);
9372             if (connection.floatingId) {
9373                 removeConnection(connection.floatingIndex, connection.floatingEndpoint, connection.floatingEndpoint.anchor, connection.floatingId, connection);
9374                 removeEndpointFromAnchorLists(connection.floatingEndpoint);
9375             }
9376 
9377             // remove from anchorLists            
9378             removeEndpointFromAnchorLists(connection.endpoints[0]);
9379             removeEndpointFromAnchorLists(connection.endpoints[1]);
9380 
9381             if (!doNotRedraw) {
9382                 self.redraw(connection.sourceId);
9383                 if (connection.targetId !== connection.sourceId) {
9384                     self.redraw(connection.targetId);
9385                 }
9386             }
9387         };
9388         this.add = function (endpoint, elementId) {
9389             _ju.addToList(_amEndpoints, elementId, endpoint);
9390         };
9391         this.changeId = function (oldId, newId) {
9392             connectionsByElementId[newId] = connectionsByElementId[oldId];
9393             _amEndpoints[newId] = _amEndpoints[oldId];
9394             delete connectionsByElementId[oldId];
9395             delete _amEndpoints[oldId];
9396         };
9397         this.getConnectionsFor = function (elementId) {
9398             return connectionsByElementId[elementId] || [];
9399         };
9400         this.getEndpointsFor = function (elementId) {
9401             return _amEndpoints[elementId] || [];
9402         };
9403         this.deleteEndpoint = function (endpoint) {
9404             _ju.removeWithFunction(_amEndpoints[endpoint.elementId], function (e) {
9405                 return e.id === endpoint.id;
9406             });
9407             removeEndpointFromAnchorLists(endpoint);
9408         };
9409         this.clearFor = function (elementId) {
9410             delete _amEndpoints[elementId];
9411             _amEndpoints[elementId] = [];
9412         };
9413         // updates the given anchor list by either updating an existing anchor's info, or adding it. this function
9414         // also removes the anchor from its previous list, if the edge it is on has changed.
9415         // all connections found along the way (those that are connected to one of the faces this function
9416         // operates on) are added to the connsToPaint list, as are their endpoints. in this way we know to repaint
9417         // them wthout having to calculate anything else about them.
9418         var _updateAnchorList = function (lists, theta, order, conn, aBoolean, otherElId, idx, reverse, edgeId, elId, connsToPaint, endpointsToPaint) {
9419             // first try to find the exact match, but keep track of the first index of a matching element id along the way.s
9420             var exactIdx = -1,
9421                 firstMatchingElIdx = -1,
9422                 endpoint = conn.endpoints[idx],
9423                 endpointId = endpoint.id,
9424                 oIdx = [1, 0][idx],
9425                 values = [
9426                     [ theta, order ],
9427                     conn,
9428                     aBoolean,
9429                     otherElId,
9430                     endpointId
9431                 ],
9432                 listToAddTo = lists[edgeId],
9433                 listToRemoveFrom = endpoint._continuousAnchorEdge ? lists[endpoint._continuousAnchorEdge] : null,
9434                 i,
9435                 candidate;
9436 
9437             if (listToRemoveFrom) {
9438                 var rIdx = _ju.findWithFunction(listToRemoveFrom, function (e) {
9439                     return e[4] === endpointId;
9440                 });
9441                 if (rIdx !== -1) {
9442                     listToRemoveFrom.splice(rIdx, 1);
9443                     // get all connections from this list
9444                     for (i = 0; i < listToRemoveFrom.length; i++) {
9445                         candidate = listToRemoveFrom[i][1];
9446                         _ju.addWithFunction(connsToPaint, candidate, function (c) {
9447                             return c.id === candidate.id;
9448                         });
9449                         _ju.addWithFunction(endpointsToPaint, listToRemoveFrom[i][1].endpoints[idx], function (e) {
9450                             return e.id === candidate.endpoints[idx].id;
9451                         });
9452                         _ju.addWithFunction(endpointsToPaint, listToRemoveFrom[i][1].endpoints[oIdx], function (e) {
9453                             return e.id === candidate.endpoints[oIdx].id;
9454                         });
9455                     }
9456                 }
9457             }
9458 
9459             for (i = 0; i < listToAddTo.length; i++) {
9460                 candidate = listToAddTo[i][1];
9461                 if (params.idx === 1 && listToAddTo[i][3] === otherElId && firstMatchingElIdx === -1) {
9462                     firstMatchingElIdx = i;
9463                 }
9464                 _ju.addWithFunction(connsToPaint, candidate, function (c) {
9465                     return c.id === candidate.id;
9466                 });
9467                 _ju.addWithFunction(endpointsToPaint, listToAddTo[i][1].endpoints[idx], function (e) {
9468                     return e.id === candidate.endpoints[idx].id;
9469                 });
9470                 _ju.addWithFunction(endpointsToPaint, listToAddTo[i][1].endpoints[oIdx], function (e) {
9471                     return e.id === candidate.endpoints[oIdx].id;
9472                 });
9473             }
9474             if (exactIdx !== -1) {
9475                 listToAddTo[exactIdx] = values;
9476             }
9477             else {
9478                 var insertIdx = reverse ? firstMatchingElIdx !== -1 ? firstMatchingElIdx : 0 : listToAddTo.length; // of course we will get this from having looked through the array shortly.
9479                 listToAddTo.splice(insertIdx, 0, values);
9480             }
9481 
9482             // store this for next time.
9483             endpoint._continuousAnchorEdge = edgeId;
9484         };
9485 
9486         //
9487         // find the entry in an endpoint's list for this connection and update its target endpoint
9488         // with the current target in the connection.
9489         // This method and sourceChanged need to be folder into one.
9490         //
9491         this.updateOtherEndpoint = function (sourceElId, oldTargetId, newTargetId, connection) {
9492             var sIndex = _ju.findWithFunction(connectionsByElementId[sourceElId], function (i) {
9493                     return i[0].id === connection.id;
9494                 }),
9495                 tIndex = _ju.findWithFunction(connectionsByElementId[oldTargetId], function (i) {
9496                     return i[0].id === connection.id;
9497                 });
9498 
9499             // update or add data for source
9500             if (sIndex !== -1) {
9501                 connectionsByElementId[sourceElId][sIndex][0] = connection;
9502                 connectionsByElementId[sourceElId][sIndex][1] = connection.endpoints[1];
9503                 connectionsByElementId[sourceElId][sIndex][2] = connection.endpoints[1].anchor.constructor === _jp.DynamicAnchor;
9504             }
9505 
9506             // remove entry for previous target (if there)
9507             if (tIndex > -1) {
9508                 connectionsByElementId[oldTargetId].splice(tIndex, 1);
9509                 // add entry for new target
9510                 _ju.addToList(connectionsByElementId, newTargetId, [connection, connection.endpoints[0], connection.endpoints[0].anchor.constructor === _jp.DynamicAnchor]);
9511             }
9512 
9513             connection.updateConnectedClass();
9514         };
9515 
9516         //
9517         // notification that the connection given has changed source from the originalId to the newId.
9518         // This involves:
9519         // 1. removing the connection from the list of connections stored for the originalId
9520         // 2. updating the source information for the target of the connection
9521         // 3. re-registering the connection in connectionsByElementId with the newId
9522         //
9523         this.sourceChanged = function (originalId, newId, connection, newElement) {
9524             if (originalId !== newId) {
9525 
9526                 connection.sourceId = newId;
9527                 connection.source = newElement;
9528 
9529                 // remove the entry that points from the old source to the target
9530                 _ju.removeWithFunction(connectionsByElementId[originalId], function (info) {
9531                     return info[0].id === connection.id;
9532                 });
9533                 // find entry for target and update it
9534                 var tIdx = _ju.findWithFunction(connectionsByElementId[connection.targetId], function (i) {
9535                     return i[0].id === connection.id;
9536                 });
9537                 if (tIdx > -1) {
9538                     connectionsByElementId[connection.targetId][tIdx][0] = connection;
9539                     connectionsByElementId[connection.targetId][tIdx][1] = connection.endpoints[0];
9540                     connectionsByElementId[connection.targetId][tIdx][2] = connection.endpoints[0].anchor.constructor === _jp.DynamicAnchor;
9541                 }
9542                 // add entry for new source
9543                 _ju.addToList(connectionsByElementId, newId, [connection, connection.endpoints[1], connection.endpoints[1].anchor.constructor === _jp.DynamicAnchor]);
9544 
9545                 // TODO SP not final on this yet. when a user drags an existing connection and it turns into a self
9546                 // loop, then this code hides the target endpoint (by removing it from the DOM) But I think this should
9547                 // occur only if the anchor is Continuous
9548                 if (connection.endpoints[1].anchor.isContinuous) {
9549                     if (connection.source === connection.target) {
9550                         connection._jsPlumb.instance.removeElement(connection.endpoints[1].canvas);
9551                     }
9552                     else {
9553                         if (connection.endpoints[1].canvas.parentNode == null) {
9554                             connection._jsPlumb.instance.appendElement(connection.endpoints[1].canvas);
9555                         }
9556                     }
9557                 }
9558 
9559                 connection.updateConnectedClass();
9560             }
9561         };
9562 
9563         //
9564         // moves the given endpoint from `currentId` to `element`.
9565         // This involves:
9566         //
9567         // 1. changing the key in _amEndpoints under which the endpoint is stored
9568         // 2. changing the source or target values in all of the endpoint's connections
9569         // 3. changing the array in connectionsByElementId in which the endpoint's connections
9570         //    are stored (done by either sourceChanged or updateOtherEndpoint)
9571         //
9572         this.rehomeEndpoint = function (ep, currentId, element) {
9573             var eps = _amEndpoints[currentId] || [],
9574                 elementId = jsPlumbInstance.getId(element);
9575 
9576             if (elementId !== currentId) {
9577                 var idx = eps.indexOf(ep);
9578                 if (idx > -1) {
9579                     var _ep = eps.splice(idx, 1)[0];
9580                     self.add(_ep, elementId);
9581                 }
9582             }
9583 
9584             for (var i = 0; i < ep.connections.length; i++) {
9585                 if (ep.connections[i].sourceId === currentId) {
9586                     self.sourceChanged(currentId, ep.elementId, ep.connections[i], ep.element);
9587                 }
9588                 else if (ep.connections[i].targetId === currentId) {
9589                     ep.connections[i].targetId = ep.elementId;
9590                     ep.connections[i].target = ep.element;
9591                     self.updateOtherEndpoint(ep.connections[i].sourceId, currentId, ep.elementId, ep.connections[i]);
9592                 }
9593             }
9594         };
9595 
9596         this.redraw = function (elementId, ui, timestamp, offsetToUI, clearEdits, doNotRecalcEndpoint) {
9597 
9598             if (!jsPlumbInstance.isSuspendDrawing()) {
9599                 // get all the endpoints for this element
9600                 var ep = _amEndpoints[elementId] || [],
9601                     endpointConnections = connectionsByElementId[elementId] || [],
9602                     connectionsToPaint = [],
9603                     endpointsToPaint = [],
9604                     anchorsToUpdate = [];
9605 
9606                 timestamp = timestamp || jsPlumbInstance.timestamp();
9607                 // offsetToUI are values that would have been calculated in the dragManager when registering
9608                 // an endpoint for an element that had a parent (somewhere in the hierarchy) that had been
9609                 // registered as draggable.
9610                 offsetToUI = offsetToUI || {left: 0, top: 0};
9611                 if (ui) {
9612                     ui = {
9613                         left: ui.left + offsetToUI.left,
9614                         top: ui.top + offsetToUI.top
9615                     };
9616                 }
9617 
9618                 // valid for one paint cycle.
9619                 var myOffset = jsPlumbInstance.updateOffset({ elId: elementId, offset: ui, recalc: false, timestamp: timestamp }),
9620                     orientationCache = {};
9621 
9622                 // actually, first we should compute the orientation of this element to all other elements to which
9623                 // this element is connected with a continuous anchor (whether both ends of the connection have
9624                 // a continuous anchor or just one)
9625 
9626                 for (var i = 0; i < endpointConnections.length; i++) {
9627                     var conn = endpointConnections[i][0],
9628                         sourceId = conn.sourceId,
9629                         targetId = conn.targetId,
9630                         sourceContinuous = conn.endpoints[0].anchor.isContinuous,
9631                         targetContinuous = conn.endpoints[1].anchor.isContinuous;
9632 
9633                     if (sourceContinuous || targetContinuous) {
9634                         var oKey = sourceId + "_" + targetId,
9635                             o = orientationCache[oKey],
9636                             oIdx = conn.sourceId === elementId ? 1 : 0;
9637 
9638                         if (sourceContinuous && !anchorLists[sourceId]) {
9639                             anchorLists[sourceId] = { top: [], right: [], bottom: [], left: [] };
9640                         }
9641                         if (targetContinuous && !anchorLists[targetId]) {
9642                             anchorLists[targetId] = { top: [], right: [], bottom: [], left: [] };
9643                         }
9644 
9645                         if (elementId !== targetId) {
9646                             jsPlumbInstance.updateOffset({ elId: targetId, timestamp: timestamp });
9647                         }
9648                         if (elementId !== sourceId) {
9649                             jsPlumbInstance.updateOffset({ elId: sourceId, timestamp: timestamp });
9650                         }
9651 
9652                         var td = jsPlumbInstance.getCachedData(targetId),
9653                             sd = jsPlumbInstance.getCachedData(sourceId);
9654 
9655                         if (targetId === sourceId && (sourceContinuous || targetContinuous)) {
9656                             // here we may want to improve this by somehow determining the face we'd like
9657                             // to put the connector on.  ideally, when drawing, the face should be calculated
9658                             // by determining which face is closest to the point at which the mouse button
9659                             // was released.  for now, we're putting it on the top face.
9660                             _updateAnchorList( anchorLists[sourceId], -Math.PI / 2, 0, conn, false, targetId, 0, false, "top", sourceId, connectionsToPaint, endpointsToPaint);
9661                             _updateAnchorList( anchorLists[targetId], -Math.PI / 2, 0, conn, false, sourceId, 1, false, "top", targetId, connectionsToPaint, endpointsToPaint);
9662                         }
9663                         else {
9664                             if (!o) {
9665                                 o = calculateOrientation(sourceId, targetId, sd.o, td.o, conn.endpoints[0].anchor, conn.endpoints[1].anchor);
9666                                 orientationCache[oKey] = o;
9667                                 // this would be a performance enhancement, but the computed angles need to be clamped to
9668                                 //the (-PI/2 -> PI/2) range in order for the sorting to work properly.
9669                                 /*  orientationCache[oKey2] = {
9670                                  orientation:o.orientation,
9671                                  a:[o.a[1], o.a[0]],
9672                                  theta:o.theta + Math.PI,
9673                                  theta2:o.theta2 + Math.PI
9674                                  };*/
9675                             }
9676                             if (sourceContinuous) {
9677                                 _updateAnchorList(anchorLists[sourceId], o.theta, 0, conn, false, targetId, 0, false, o.a[0], sourceId, connectionsToPaint, endpointsToPaint);
9678                             }
9679                             if (targetContinuous) {
9680                                 _updateAnchorList(anchorLists[targetId], o.theta2, -1, conn, true, sourceId, 1, true, o.a[1], targetId, connectionsToPaint, endpointsToPaint);
9681                             }
9682                         }
9683 
9684                         if (sourceContinuous) {
9685                             _ju.addWithFunction(anchorsToUpdate, sourceId, function (a) {
9686                                 return a === sourceId;
9687                             });
9688                         }
9689                         if (targetContinuous) {
9690                             _ju.addWithFunction(anchorsToUpdate, targetId, function (a) {
9691                                 return a === targetId;
9692                             });
9693                         }
9694                         _ju.addWithFunction(connectionsToPaint, conn, function (c) {
9695                             return c.id === conn.id;
9696                         });
9697                         if ((sourceContinuous && oIdx === 0) || (targetContinuous && oIdx === 1)) {
9698                             _ju.addWithFunction(endpointsToPaint, conn.endpoints[oIdx], function (e) {
9699                                 return e.id === conn.endpoints[oIdx].id;
9700                             });
9701                         }
9702                     }
9703                 }
9704 
9705                 // place Endpoints whose anchors are continuous but have no Connections
9706                 for (i = 0; i < ep.length; i++) {
9707                     if (ep[i].connections.length === 0 && ep[i].anchor.isContinuous) {
9708                         if (!anchorLists[elementId]) {
9709                             anchorLists[elementId] = { top: [], right: [], bottom: [], left: [] };
9710                         }
9711                         _updateAnchorList(anchorLists[elementId], -Math.PI / 2, 0, {endpoints: [ep[i], ep[i]], paint: function () {
9712                         }}, false, elementId, 0, false, ep[i].anchor.getDefaultFace(), elementId, connectionsToPaint, endpointsToPaint);
9713                         _ju.addWithFunction(anchorsToUpdate, elementId, function (a) {
9714                             return a === elementId;
9715                         });
9716                     }
9717                 }
9718 
9719                 // now place all the continuous anchors we need to;
9720                 for (i = 0; i < anchorsToUpdate.length; i++) {
9721                     placeAnchors(anchorsToUpdate[i], anchorLists[anchorsToUpdate[i]]);
9722                 }
9723 
9724                 // now that continuous anchors have been placed, paint all the endpoints for this element
9725                 // TODO performance: add the endpoint ids to a temp array, and then when iterating in the next
9726                 // loop, check that we didn't just paint that endpoint. we can probably shave off a few more milliseconds this way.
9727                 for (i = 0; i < ep.length; i++) {
9728                     ep[i].paint({ timestamp: timestamp, offset: myOffset, dimensions: myOffset.s, recalc: doNotRecalcEndpoint !== true });
9729                 }
9730 
9731                 // ... and any other endpoints we came across as a result of the continuous anchors.
9732                 for (i = 0; i < endpointsToPaint.length; i++) {
9733                     var cd = jsPlumbInstance.getCachedData(endpointsToPaint[i].elementId);
9734                     endpointsToPaint[i].paint({ timestamp: timestamp, offset: cd, dimensions: cd.s });
9735                 }
9736 
9737                 // paint all the standard and "dynamic connections", which are connections whose other anchor is
9738                 // static and therefore does need to be recomputed; we make sure that happens only one time.
9739 
9740                 // TODO we could have compiled a list of these in the first pass through connections; might save some time.
9741                 for (i = 0; i < endpointConnections.length; i++) {
9742                     var otherEndpoint = endpointConnections[i][1];
9743                     if (otherEndpoint.anchor.constructor === _jp.DynamicAnchor) {
9744                         otherEndpoint.paint({ elementWithPrecedence: elementId, timestamp: timestamp });
9745                         _ju.addWithFunction(connectionsToPaint, endpointConnections[i][0], function (c) {
9746                             return c.id === endpointConnections[i][0].id;
9747                         });
9748                         // all the connections for the other endpoint now need to be repainted
9749                         for (var k = 0; k < otherEndpoint.connections.length; k++) {
9750                             if (otherEndpoint.connections[k] !== endpointConnections[i][0]) {
9751                                 _ju.addWithFunction(connectionsToPaint, otherEndpoint.connections[k], function (c) {
9752                                     return c.id === otherEndpoint.connections[k].id;
9753                                 });
9754                             }
9755                         }
9756                     } else if (otherEndpoint.anchor.constructor === _jp.Anchor) {
9757                         _ju.addWithFunction(connectionsToPaint, endpointConnections[i][0], function (c) {
9758                             return c.id === endpointConnections[i][0].id;
9759                         });
9760                     }
9761                 }
9762 
9763                 // paint current floating connection for this element, if there is one.
9764                 var fc = floatingConnections[elementId];
9765                 if (fc) {
9766                     fc.paint({timestamp: timestamp, recalc: false, elId: elementId});
9767                 }
9768 
9769                 // paint all the connections
9770                 for (i = 0; i < connectionsToPaint.length; i++) {
9771                     connectionsToPaint[i].paint({elId: elementId, timestamp: timestamp, recalc: false, clearEdits: clearEdits});
9772                 }
9773             }
9774         };
9775 
9776         var ContinuousAnchor = function (anchorParams) {
9777             _ju.EventGenerator.apply(this);
9778             this.type = "Continuous";
9779             this.isDynamic = true;
9780             this.isContinuous = true;
9781             var faces = anchorParams.faces || ["top", "right", "bottom", "left"],
9782                 clockwise = !(anchorParams.clockwise === false),
9783                 availableFaces = { },
9784                 opposites = { "top": "bottom", "right": "left", "left": "right", "bottom": "top" },
9785                 clockwiseOptions = { "top": "right", "right": "bottom", "left": "top", "bottom": "left" },
9786                 antiClockwiseOptions = { "top": "left", "right": "top", "left": "bottom", "bottom": "right" },
9787                 secondBest = clockwise ? clockwiseOptions : antiClockwiseOptions,
9788                 lastChoice = clockwise ? antiClockwiseOptions : clockwiseOptions,
9789                 cssClass = anchorParams.cssClass || "";
9790 
9791             for (var i = 0; i < faces.length; i++) {
9792                 availableFaces[faces[i]] = true;
9793             }
9794 
9795             this.getDefaultFace = function () {
9796                 return faces.length === 0 ? "top" : faces[0];
9797             };
9798 
9799             // if the given edge is supported, returns it. otherwise looks for a substitute that _is_
9800             // supported. if none supported we also return the request edge.
9801             this.verifyEdge = function (edge) {
9802                 if (availableFaces[edge]) {
9803                     return edge;
9804                 }
9805                 else if (availableFaces[opposites[edge]]) {
9806                     return opposites[edge];
9807                 }
9808                 else if (availableFaces[secondBest[edge]]) {
9809                     return secondBest[edge];
9810                 }
9811                 else if (availableFaces[lastChoice[edge]]) {
9812                     return lastChoice[edge];
9813                 }
9814                 return edge; // we have to give them something.
9815             };
9816 
9817             this.isEdgeSupported = function (edge) {
9818                 return availableFaces[edge] === true;
9819             };
9820 
9821             this.compute = function (params) {
9822                 return userDefinedContinuousAnchorLocations[params.element.id] || continuousAnchorLocations[params.element.id] || [0, 0];
9823             };
9824             this.getCurrentLocation = function (params) {
9825                 return userDefinedContinuousAnchorLocations[params.element.id] || continuousAnchorLocations[params.element.id] || [0, 0];
9826             };
9827             this.getOrientation = function (endpoint) {
9828                 return continuousAnchorOrientations[endpoint.id] || [0, 0];
9829             };
9830             this.clearUserDefinedLocation = function () {
9831                 delete userDefinedContinuousAnchorLocations[anchorParams.elementId];
9832             };
9833             this.setUserDefinedLocation = function (loc) {
9834                 userDefinedContinuousAnchorLocations[anchorParams.elementId] = loc;
9835             };
9836             this.getCssClass = function () {
9837                 return cssClass;
9838             };
9839         };
9840 
9841         // continuous anchors
9842         jsPlumbInstance.continuousAnchorFactory = {
9843             get: function (params) {
9844                 return new ContinuousAnchor(params);
9845             },
9846             clear: function (elementId) {
9847                 delete userDefinedContinuousAnchorLocations[elementId];
9848                 delete continuousAnchorLocations[elementId];
9849             }
9850         };
9851     };
9852 
9853     /**
9854      * Anchors model a position on some element at which an Endpoint may be located.  They began as a first class citizen of jsPlumb, ie. a user
9855      * was required to create these themselves, but over time this has been replaced by the concept of referring to them either by name (eg. "TopMiddle"),
9856      * or by an array describing their coordinates (eg. [ 0, 0.5, 0, -1 ], which is the same as "TopMiddle").  jsPlumb now handles all of the
9857      * creation of Anchors without user intervention.
9858      */
9859     _jp.Anchor = function (params) {
9860         this.x = params.x || 0;
9861         this.y = params.y || 0;
9862         this.elementId = params.elementId;
9863         this.cssClass = params.cssClass || "";
9864         this.userDefinedLocation = null;
9865         this.orientation = params.orientation || [ 0, 0 ];
9866         this.lastReturnValue = null;
9867         this.offsets = params.offsets || [ 0, 0 ];
9868         this.timestamp = null;
9869 
9870         _ju.EventGenerator.apply(this);
9871 
9872         this.compute = function (params) {
9873 
9874             var xy = params.xy, wh = params.wh, timestamp = params.timestamp;
9875 
9876             if (params.clearUserDefinedLocation) {
9877                 this.userDefinedLocation = null;
9878             }
9879 
9880             if (timestamp && timestamp === this.timestamp) {
9881                 return this.lastReturnValue;
9882             }
9883 
9884             if (this.userDefinedLocation != null) {
9885                 this.lastReturnValue = this.userDefinedLocation;
9886             }
9887             else {
9888                 this.lastReturnValue = [ xy[0] + (this.x * wh[0]) + this.offsets[0], xy[1] + (this.y * wh[1]) + this.offsets[1] ];
9889             }
9890 
9891             this.timestamp = timestamp;
9892             return this.lastReturnValue;
9893         };
9894 
9895         this.getCurrentLocation = function (params) {
9896             params = params || {};
9897             return (this.lastReturnValue == null || (params.timestamp != null && this.timestamp !== params.timestamp)) ? this.compute(params) : this.lastReturnValue;
9898         };
9899     };
9900     _ju.extend(_jp.Anchor, _ju.EventGenerator, {
9901         equals: function (anchor) {
9902             if (!anchor) {
9903                 return false;
9904             }
9905             var ao = anchor.getOrientation(),
9906                 o = this.getOrientation();
9907             return this.x === anchor.x && this.y === anchor.y && this.offsets[0] === anchor.offsets[0] && this.offsets[1] === anchor.offsets[1] && o[0] === ao[0] && o[1] === ao[1];
9908         },
9909         getUserDefinedLocation: function () {
9910             return this.userDefinedLocation;
9911         },
9912         setUserDefinedLocation: function (l) {
9913             this.userDefinedLocation = l;
9914         },
9915         clearUserDefinedLocation: function () {
9916             this.userDefinedLocation = null;
9917         },
9918         getOrientation: function () {
9919             return this.orientation;
9920         },
9921         getCssClass: function () {
9922             return this.cssClass;
9923         }
9924     });
9925 
9926     /**
9927      * An Anchor that floats. its orientation is computed dynamically from
9928      * its position relative to the anchor it is floating relative to.  It is used when creating
9929      * a connection through drag and drop.
9930      *
9931      * TODO FloatingAnchor could totally be refactored to extend Anchor just slightly.
9932      */
9933     _jp.FloatingAnchor = function (params) {
9934 
9935         _jp.Anchor.apply(this, arguments);
9936 
9937         // this is the anchor that this floating anchor is referenced to for
9938         // purposes of calculating the orientation.
9939         var ref = params.reference,
9940             // the canvas this refers to.
9941             refCanvas = params.referenceCanvas,
9942             size = _jp.getSize(refCanvas),
9943             // these are used to store the current relative position of our
9944             // anchor wrt the reference anchor. they only indicate
9945             // direction, so have a value of 1 or -1 (or, very rarely, 0). these
9946             // values are written by the compute method, and read
9947             // by the getOrientation method.
9948             xDir = 0, yDir = 0,
9949             // temporary member used to store an orientation when the floating
9950             // anchor is hovering over another anchor.
9951             orientation = null,
9952             _lastResult = null;
9953 
9954         // clear from parent. we want floating anchor orientation to always be computed.
9955         this.orientation = null;
9956 
9957         // set these to 0 each; they are used by certain types of connectors in the loopback case,
9958         // when the connector is trying to clear the element it is on. but for floating anchor it's not
9959         // very important.
9960         this.x = 0;
9961         this.y = 0;
9962 
9963         this.isFloating = true;
9964 
9965         this.compute = function (params) {
9966             var xy = params.xy,
9967                 result = [ xy[0] + (size[0] / 2), xy[1] + (size[1] / 2) ]; // return origin of the element. we may wish to improve this so that any object can be the drag proxy.
9968             _lastResult = result;
9969             return result;
9970         };
9971 
9972         this.getOrientation = function (_endpoint) {
9973             if (orientation) {
9974                 return orientation;
9975             }
9976             else {
9977                 var o = ref.getOrientation(_endpoint);
9978                 // here we take into account the orientation of the other
9979                 // anchor: if it declares zero for some direction, we declare zero too. this might not be the most awesome. perhaps we can come
9980                 // up with a better way. it's just so that the line we draw looks like it makes sense. maybe this wont make sense.
9981                 return [ Math.abs(o[0]) * xDir * -1,
9982                         Math.abs(o[1]) * yDir * -1 ];
9983             }
9984         };
9985 
9986         /**
9987          * notification the endpoint associated with this anchor is hovering
9988          * over another anchor; we want to assume that anchor's orientation
9989          * for the duration of the hover.
9990          */
9991         this.over = function (anchor, endpoint) {
9992             orientation = anchor.getOrientation(endpoint);
9993         };
9994 
9995         /**
9996          * notification the endpoint associated with this anchor is no
9997          * longer hovering over another anchor; we should resume calculating
9998          * orientation as we normally do.
9999          */
10000         this.out = function () {
10001             orientation = null;
10002         };
10003 
10004         this.getCurrentLocation = function (params) {
10005             return _lastResult == null ? this.compute(params) : _lastResult;
10006         };
10007     };
10008     _ju.extend(_jp.FloatingAnchor, _jp.Anchor);
10009 
10010     var _convertAnchor = function (anchor, jsPlumbInstance, elementId) {
10011         return anchor.constructor === _jp.Anchor ? anchor : jsPlumbInstance.makeAnchor(anchor, elementId, jsPlumbInstance);
10012     };
10013 
10014     /* 
10015      * A DynamicAnchor is an Anchor that contains a list of other Anchors, which it cycles
10016      * through at compute time to find the one that is located closest to
10017      * the center of the target element, and returns that Anchor's compute
10018      * method result. this causes endpoints to follow each other with
10019      * respect to the orientation of their target elements, which is a useful
10020      * feature for some applications.
10021      * 
10022      */
10023     _jp.DynamicAnchor = function (params) {
10024         _jp.Anchor.apply(this, arguments);
10025 
10026         this.isDynamic = true;
10027         this.anchors = [];
10028         this.elementId = params.elementId;
10029         this.jsPlumbInstance = params.jsPlumbInstance;
10030 
10031         for (var i = 0; i < params.anchors.length; i++) {
10032             this.anchors[i] = _convertAnchor(params.anchors[i], this.jsPlumbInstance, this.elementId);
10033         }
10034 
10035         this.getAnchors = function () {
10036             return this.anchors;
10037         };
10038         this.locked = false;
10039         var _curAnchor = this.anchors.length > 0 ? this.anchors[0] : null,
10040             _lastAnchor = _curAnchor,
10041             self = this,
10042 
10043         // helper method to calculate the distance between the centers of the two elements.
10044             _distance = function (anchor, cx, cy, xy, wh) {
10045                 var ax = xy[0] + (anchor.x * wh[0]), ay = xy[1] + (anchor.y * wh[1]),
10046                     acx = xy[0] + (wh[0] / 2), acy = xy[1] + (wh[1] / 2);
10047                 return (Math.sqrt(Math.pow(cx - ax, 2) + Math.pow(cy - ay, 2)) +
10048                     Math.sqrt(Math.pow(acx - ax, 2) + Math.pow(acy - ay, 2)));
10049             },
10050         // default method uses distance between element centers.  you can provide your own method in the dynamic anchor
10051         // constructor (and also to jsPlumb.makeDynamicAnchor). the arguments to it are four arrays:
10052         // xy - xy loc of the anchor's element
10053         // wh - anchor's element's dimensions
10054         // txy - xy loc of the element of the other anchor in the connection
10055         // twh - dimensions of the element of the other anchor in the connection.
10056         // anchors - the list of selectable anchors
10057             _anchorSelector = params.selector || function (xy, wh, txy, twh, anchors) {
10058                 var cx = txy[0] + (twh[0] / 2), cy = txy[1] + (twh[1] / 2);
10059                 var minIdx = -1, minDist = Infinity;
10060                 for (var i = 0; i < anchors.length; i++) {
10061                     var d = _distance(anchors[i], cx, cy, xy, wh);
10062                     if (d < minDist) {
10063                         minIdx = i + 0;
10064                         minDist = d;
10065                     }
10066                 }
10067                 return anchors[minIdx];
10068             };
10069 
10070         this.compute = function (params) {
10071             var xy = params.xy, wh = params.wh, txy = params.txy, twh = params.twh;
10072 
10073             this.timestamp = params.timestamp;
10074 
10075             var udl = self.getUserDefinedLocation();
10076             if (udl != null) {
10077                 return udl;
10078             }
10079 
10080             // if anchor is locked or an opposite element was not given, we
10081             // maintain our state. anchor will be locked
10082             // if it is the source of a drag and drop.
10083             if (this.locked || txy == null || twh == null) {
10084                 return _curAnchor.compute(params);
10085             }
10086             else {
10087                 params.timestamp = null; // otherwise clear this, i think. we want the anchor to compute.
10088             }
10089 
10090             _curAnchor = _anchorSelector(xy, wh, txy, twh, this.anchors);
10091             this.x = _curAnchor.x;
10092             this.y = _curAnchor.y;
10093 
10094             if (_curAnchor !== _lastAnchor) {
10095                 this.fire("anchorChanged", _curAnchor);
10096             }
10097 
10098             _lastAnchor = _curAnchor;
10099 
10100             return _curAnchor.compute(params);
10101         };
10102 
10103         this.getCurrentLocation = function (params) {
10104             return this.getUserDefinedLocation() || (_curAnchor != null ? _curAnchor.getCurrentLocation(params) : null);
10105         };
10106 
10107         this.getOrientation = function (_endpoint) {
10108             return _curAnchor != null ? _curAnchor.getOrientation(_endpoint) : [ 0, 0 ];
10109         };
10110         this.over = function (anchor, endpoint) {
10111             if (_curAnchor != null) {
10112                 _curAnchor.over(anchor, endpoint);
10113             }
10114         };
10115         this.out = function () {
10116             if (_curAnchor != null) {
10117                 _curAnchor.out();
10118             }
10119         };
10120 
10121         this.getCssClass = function () {
10122             return (_curAnchor && _curAnchor.getCssClass()) || "";
10123         };
10124     };
10125     _ju.extend(_jp.DynamicAnchor, _jp.Anchor);
10126 
10127 // -------- basic anchors ------------------    
10128     var _curryAnchor = function (x, y, ox, oy, type, fnInit) {
10129         _jp.Anchors[type] = function (params) {
10130             var a = params.jsPlumbInstance.makeAnchor([ x, y, ox, oy, 0, 0 ], params.elementId, params.jsPlumbInstance);
10131             a.type = type;
10132             if (fnInit) {
10133                 fnInit(a, params);
10134             }
10135             return a;
10136         };
10137     };
10138 
10139     _curryAnchor(0.5, 0, 0, -1, "TopCenter");
10140     _curryAnchor(0.5, 1, 0, 1, "BottomCenter");
10141     _curryAnchor(0, 0.5, -1, 0, "LeftMiddle");
10142     _curryAnchor(1, 0.5, 1, 0, "RightMiddle");
10143 
10144     _curryAnchor(0.5, 0, 0, -1, "Top");
10145     _curryAnchor(0.5, 1, 0, 1, "Bottom");
10146     _curryAnchor(0, 0.5, -1, 0, "Left");
10147     _curryAnchor(1, 0.5, 1, 0, "Right");
10148     _curryAnchor(0.5, 0.5, 0, 0, "Center");
10149     _curryAnchor(1, 0, 0, -1, "TopRight");
10150     _curryAnchor(1, 1, 0, 1, "BottomRight");
10151     _curryAnchor(0, 0, 0, -1, "TopLeft");
10152     _curryAnchor(0, 1, 0, 1, "BottomLeft");
10153 
10154 // ------- dynamic anchors -------------------    
10155 
10156     // default dynamic anchors chooses from Top, Right, Bottom, Left
10157     _jp.Defaults.DynamicAnchors = function (params) {
10158         return params.jsPlumbInstance.makeAnchors(["TopCenter", "RightMiddle", "BottomCenter", "LeftMiddle"], params.elementId, params.jsPlumbInstance);
10159     };
10160 
10161     // default dynamic anchors bound to name 'AutoDefault'
10162     _jp.Anchors.AutoDefault = function (params) {
10163         var a = params.jsPlumbInstance.makeDynamicAnchor(_jp.Defaults.DynamicAnchors(params));
10164         a.type = "AutoDefault";
10165         return a;
10166     };
10167 
10168 // ------- continuous anchors -------------------    
10169 
10170     var _curryContinuousAnchor = function (type, faces) {
10171         _jp.Anchors[type] = function (params) {
10172             var a = params.jsPlumbInstance.makeAnchor(["Continuous", { faces: faces }], params.elementId, params.jsPlumbInstance);
10173             a.type = type;
10174             return a;
10175         };
10176     };
10177 
10178     _jp.Anchors.Continuous = function (params) {
10179         return params.jsPlumbInstance.continuousAnchorFactory.get(params);
10180     };
10181 
10182     _curryContinuousAnchor("ContinuousLeft", ["left"]);
10183     _curryContinuousAnchor("ContinuousTop", ["top"]);
10184     _curryContinuousAnchor("ContinuousBottom", ["bottom"]);
10185     _curryContinuousAnchor("ContinuousRight", ["right"]);
10186 
10187 // ------- position assign anchors -------------------    
10188 
10189     // this anchor type lets you assign the position at connection time.
10190     _curryAnchor(0, 0, 0, 0, "Assign", function (anchor, params) {
10191         // find what to use as the "position finder". the user may have supplied a String which represents
10192         // the id of a position finder in jsPlumb.AnchorPositionFinders, or the user may have supplied the
10193         // position finder as a function.  we find out what to use and then set it on the anchor.
10194         var pf = params.position || "Fixed";
10195         anchor.positionFinder = pf.constructor === String ? params.jsPlumbInstance.AnchorPositionFinders[pf] : pf;
10196         // always set the constructor params; the position finder might need them later (the Grid one does,
10197         // for example)
10198         anchor.constructorParams = params;
10199     });
10200 
10201     // these are the default anchor positions finders, which are used by the makeTarget function.  supplying
10202     // a position finder argument to that function allows you to specify where the resulting anchor will
10203     // be located
10204     root.jsPlumbInstance.prototype.AnchorPositionFinders = {
10205         "Fixed": function (dp, ep, es) {
10206             return [ (dp.left - ep.left) / es[0], (dp.top - ep.top) / es[1] ];
10207         },
10208         "Grid": function (dp, ep, es, params) {
10209             var dx = dp.left - ep.left, dy = dp.top - ep.top,
10210                 gx = es[0] / (params.grid[0]), gy = es[1] / (params.grid[1]),
10211                 mx = Math.floor(dx / gx), my = Math.floor(dy / gy);
10212             return [ ((mx * gx) + (gx / 2)) / es[0], ((my * gy) + (gy / 2)) / es[1] ];
10213         }
10214     };
10215 
10216 // ------- perimeter anchors -------------------    
10217 
10218     _jp.Anchors.Perimeter = function (params) {
10219         params = params || {};
10220         var anchorCount = params.anchorCount || 60,
10221             shape = params.shape;
10222 
10223         if (!shape) {
10224             throw new Error("no shape supplied to Perimeter Anchor type");
10225         }
10226 
10227         var _circle = function () {
10228                 var r = 0.5, step = Math.PI * 2 / anchorCount, current = 0, a = [];
10229                 for (var i = 0; i < anchorCount; i++) {
10230                     var x = r + (r * Math.sin(current)),
10231                         y = r + (r * Math.cos(current));
10232                     a.push([ x, y, 0, 0 ]);
10233                     current += step;
10234                 }
10235                 return a;
10236             },
10237             _path = function (segments) {
10238                 var anchorsPerFace = anchorCount / segments.length, a = [],
10239                     _computeFace = function (x1, y1, x2, y2, fractionalLength) {
10240                         anchorsPerFace = anchorCount * fractionalLength;
10241                         var dx = (x2 - x1) / anchorsPerFace, dy = (y2 - y1) / anchorsPerFace;
10242                         for (var i = 0; i < anchorsPerFace; i++) {
10243                             a.push([
10244                                     x1 + (dx * i),
10245                                     y1 + (dy * i),
10246                                 0,
10247                                 0
10248                             ]);
10249                         }
10250                     };
10251 
10252                 for (var i = 0; i < segments.length; i++) {
10253                     _computeFace.apply(null, segments[i]);
10254                 }
10255 
10256                 return a;
10257             },
10258             _shape = function (faces) {
10259                 var s = [];
10260                 for (var i = 0; i < faces.length; i++) {
10261                     s.push([faces[i][0], faces[i][1], faces[i][2], faces[i][3], 1 / faces.length]);
10262                 }
10263                 return _path(s);
10264             },
10265             _rectangle = function () {
10266                 return _shape([
10267                     [ 0, 0, 1, 0 ],
10268                     [ 1, 0, 1, 1 ],
10269                     [ 1, 1, 0, 1 ],
10270                     [ 0, 1, 0, 0 ]
10271                 ]);
10272             };
10273 
10274         var _shapes = {
10275                 "Circle": _circle,
10276                 "Ellipse": _circle,
10277                 "Diamond": function () {
10278                     return _shape([
10279                         [ 0.5, 0, 1, 0.5 ],
10280                         [ 1, 0.5, 0.5, 1 ],
10281                         [ 0.5, 1, 0, 0.5 ],
10282                         [ 0, 0.5, 0.5, 0 ]
10283                     ]);
10284                 },
10285                 "Rectangle": _rectangle,
10286                 "Square": _rectangle,
10287                 "Triangle": function () {
10288                     return _shape([
10289                         [ 0.5, 0, 1, 1 ],
10290                         [ 1, 1, 0, 1 ],
10291                         [ 0, 1, 0.5, 0]
10292                     ]);
10293                 },
10294                 "Path": function (params) {
10295                     var points = params.points, p = [], tl = 0;
10296                     for (var i = 0; i < points.length - 1; i++) {
10297                         var l = Math.sqrt(Math.pow(points[i][2] - points[i][0]) + Math.pow(points[i][3] - points[i][1]));
10298                         tl += l;
10299                         p.push([points[i][0], points[i][1], points[i + 1][0], points[i + 1][1], l]);
10300                     }
10301                     for (var j = 0; j < p.length; j++) {
10302                         p[j][4] = p[j][4] / tl;
10303                     }
10304                     return _path(p);
10305                 }
10306             },
10307             _rotate = function (points, amountInDegrees) {
10308                 var o = [], theta = amountInDegrees / 180 * Math.PI;
10309                 for (var i = 0; i < points.length; i++) {
10310                     var _x = points[i][0] - 0.5,
10311                         _y = points[i][1] - 0.5;
10312 
10313                     o.push([
10314                             0.5 + ((_x * Math.cos(theta)) - (_y * Math.sin(theta))),
10315                             0.5 + ((_x * Math.sin(theta)) + (_y * Math.cos(theta))),
10316                         points[i][2],
10317                         points[i][3]
10318                     ]);
10319                 }
10320                 return o;
10321             };
10322 
10323         if (!_shapes[shape]) {
10324             throw new Error("Shape [" + shape + "] is unknown by Perimeter Anchor type");
10325         }
10326 
10327         var da = _shapes[shape](params);
10328         if (params.rotation) {
10329             da = _rotate(da, params.rotation);
10330         }
10331         var a = params.jsPlumbInstance.makeDynamicAnchor(da);
10332         a.type = "Perimeter";
10333         return a;
10334     };
10335 }).call(typeof window !== 'undefined' ? window : this);
10336 /*
10337  * jsPlumb Community Edition
10338  * 
10339  * Provides a way to visually connect elements on an HTML page, using SVG.
10340  * 
10341  * This file contains the default Connectors, Endpoint and Overlay definitions.
10342  *
10343  * Copyright (c) 2010 - 2017 jsPlumb (hello@jsplumbtoolkit.com)
10344  * 
10345  * https://jsplumbtoolkit.com
10346  * https://github.com/jsplumb/jsplumb
10347  * 
10348  * Dual licensed under the MIT and GPL2 licenses.
10349  */
10350 ;
10351 (function () {
10352 
10353     "use strict";
10354     var root = this, _jp = root.jsPlumb, _ju = root.jsPlumbUtil, _jg = root.Biltong;
10355 
10356     _jp.Segments = {
10357 
10358         /*
10359          * Class: AbstractSegment
10360          * A Connector is made up of 1..N Segments, each of which has a Type, such as 'Straight', 'Arc',
10361          * 'Bezier'. This is new from 1.4.2, and gives us a lot more flexibility when drawing connections: things such
10362          * as rounded corners for flowchart connectors, for example, or a straight line stub for Bezier connections, are
10363          * much easier to do now.
10364          *
10365          * A Segment is responsible for providing coordinates for painting it, and also must be able to report its length.
10366          * 
10367          */
10368         AbstractSegment: function (params) {
10369             this.params = params;
10370 
10371             /**
10372              * Function: findClosestPointOnPath
10373              * Finds the closest point on this segment to the given [x, y],
10374              * returning both the x and y of the point plus its distance from
10375              * the supplied point, and its location along the length of the
10376              * path inscribed by the segment.  This implementation returns
10377              * Infinity for distance and null values for everything else;
10378              * subclasses are expected to override.
10379              */
10380             this.findClosestPointOnPath = function (x, y) {
10381                 return {
10382                     d: Infinity,
10383                     x: null,
10384                     y: null,
10385                     l: null
10386                 };
10387             };
10388 
10389             this.getBounds = function () {
10390                 return {
10391                     minX: Math.min(params.x1, params.x2),
10392                     minY: Math.min(params.y1, params.y2),
10393                     maxX: Math.max(params.x1, params.x2),
10394                     maxY: Math.max(params.y1, params.y2)
10395                 };
10396             };
10397         },
10398         Straight: function (params) {
10399             var _super = _jp.Segments.AbstractSegment.apply(this, arguments),
10400                 length, m, m2, x1, x2, y1, y2,
10401                 _recalc = function () {
10402                     length = Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
10403                     m = _jg.gradient({x: x1, y: y1}, {x: x2, y: y2});
10404                     m2 = -1 / m;
10405                 };
10406 
10407             this.type = "Straight";
10408 
10409             this.getLength = function () {
10410                 return length;
10411             };
10412             this.getGradient = function () {
10413                 return m;
10414             };
10415 
10416             this.getCoordinates = function () {
10417                 return { x1: x1, y1: y1, x2: x2, y2: y2 };
10418             };
10419             this.setCoordinates = function (coords) {
10420                 x1 = coords.x1;
10421                 y1 = coords.y1;
10422                 x2 = coords.x2;
10423                 y2 = coords.y2;
10424                 _recalc();
10425             };
10426             this.setCoordinates({x1: params.x1, y1: params.y1, x2: params.x2, y2: params.y2});
10427 
10428             this.getBounds = function () {
10429                 return {
10430                     minX: Math.min(x1, x2),
10431                     minY: Math.min(y1, y2),
10432                     maxX: Math.max(x1, x2),
10433                     maxY: Math.max(y1, y2)
10434                 };
10435             };
10436 
10437             /**
10438              * returns the point on the segment's path that is 'location' along the length of the path, where 'location' is a decimal from
10439              * 0 to 1 inclusive. for the straight line segment this is simple maths.
10440              */
10441             this.pointOnPath = function (location, absolute) {
10442                 if (location === 0 && !absolute) {
10443                     return { x: x1, y: y1 };
10444                 }
10445                 else if (location === 1 && !absolute) {
10446                     return { x: x2, y: y2 };
10447                 }
10448                 else {
10449                     var l = absolute ? location > 0 ? location : length + location : location * length;
10450                     return _jg.pointOnLine({x: x1, y: y1}, {x: x2, y: y2}, l);
10451                 }
10452             };
10453 
10454             /**
10455              * returns the gradient of the segment at the given point - which for us is constant.
10456              */
10457             this.gradientAtPoint = function (_) {
10458                 return m;
10459             };
10460 
10461             /**
10462              * returns the point on the segment's path that is 'distance' along the length of the path from 'location', where
10463              * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels.
10464              * this hands off to jsPlumbUtil to do the maths, supplying two points and the distance.
10465              */
10466             this.pointAlongPathFrom = function (location, distance, absolute) {
10467                 var p = this.pointOnPath(location, absolute),
10468                     farAwayPoint = distance <= 0 ? {x: x1, y: y1} : {x: x2, y: y2 };
10469 
10470                 /*
10471                  location == 1 ? {
10472                  x:x1 + ((x2 - x1) * 10),
10473                  y:y1 + ((y1 - y2) * 10)
10474                  } :
10475                  */
10476 
10477                 if (distance <= 0 && Math.abs(distance) > 1) {
10478                     distance *= -1;
10479                 }
10480 
10481                 return _jg.pointOnLine(p, farAwayPoint, distance);
10482             };
10483 
10484             // is c between a and b?
10485             var within = function (a, b, c) {
10486                 return c >= Math.min(a, b) && c <= Math.max(a, b);
10487             };
10488             // find which of a and b is closest to c
10489             var closest = function (a, b, c) {
10490                 return Math.abs(c - a) < Math.abs(c - b) ? a : b;
10491             };
10492 
10493             /**
10494              Function: findClosestPointOnPath
10495              Finds the closest point on this segment to [x,y]. See
10496              notes on this method in AbstractSegment.
10497              */
10498             this.findClosestPointOnPath = function (x, y) {
10499                 var out = {
10500                     d: Infinity,
10501                     x: null,
10502                     y: null,
10503                     l: null,
10504                     x1: x1,
10505                     x2: x2,
10506                     y1: y1,
10507                     y2: y2
10508                 };
10509 
10510                 if (m === 0) {
10511                     out.y = y1;
10512                     out.x = within(x1, x2, x) ? x : closest(x1, x2, x);
10513                 }
10514                 else if (m === Infinity || m === -Infinity) {
10515                     out.x = x1;
10516                     out.y = within(y1, y2, y) ? y : closest(y1, y2, y);
10517                 }
10518                 else {
10519                     // closest point lies on normal from given point to this line.  
10520                     var b = y1 - (m * x1),
10521                         b2 = y - (m2 * x),
10522                     // y1 = m.x1 + b and y1 = m2.x1 + b2
10523                     // so m.x1 + b = m2.x1 + b2
10524                     // x1(m - m2) = b2 - b
10525                     // x1 = (b2 - b) / (m - m2)
10526                         _x1 = (b2 - b) / (m - m2),
10527                         _y1 = (m * _x1) + b;
10528 
10529                     out.x = within(x1, x2, _x1) ? _x1 : closest(x1, x2, _x1);//_x1;
10530                     out.y = within(y1, y2, _y1) ? _y1 : closest(y1, y2, _y1);//_y1;
10531                 }
10532 
10533                 var fractionInSegment = _jg.lineLength([ out.x, out.y ], [ x1, y1 ]);
10534                 out.d = _jg.lineLength([x, y], [out.x, out.y]);
10535                 out.l = fractionInSegment / length;
10536                 return out;
10537             };
10538         },
10539 
10540         /*
10541          Arc Segment. You need to supply:
10542 
10543          r   -   radius
10544          cx  -   center x for the arc
10545          cy  -   center y for the arc
10546          ac  -   whether the arc is anticlockwise or not. default is clockwise.
10547 
10548          and then either:
10549 
10550          startAngle  -   startAngle for the arc.
10551          endAngle    -   endAngle for the arc.
10552 
10553          or:
10554 
10555          x1          -   x for start point
10556          y1          -   y for start point
10557          x2          -   x for end point
10558          y2          -   y for end point
10559 
10560          */
10561         Arc: function (params) {
10562             var _super = _jp.Segments.AbstractSegment.apply(this, arguments),
10563                 _calcAngle = function (_x, _y) {
10564                     return _jg.theta([params.cx, params.cy], [_x, _y]);
10565                 },
10566                 _calcAngleForLocation = function (segment, location) {
10567                     if (segment.anticlockwise) {
10568                         var sa = segment.startAngle < segment.endAngle ? segment.startAngle + TWO_PI : segment.startAngle,
10569                             s = Math.abs(sa - segment.endAngle);
10570                         return sa - (s * location);
10571                     }
10572                     else {
10573                         var ea = segment.endAngle < segment.startAngle ? segment.endAngle + TWO_PI : segment.endAngle,
10574                             ss = Math.abs(ea - segment.startAngle);
10575 
10576                         return segment.startAngle + (ss * location);
10577                     }
10578                 },
10579                 TWO_PI = 2 * Math.PI;
10580 
10581             this.radius = params.r;
10582             this.anticlockwise = params.ac;
10583             this.type = "Arc";
10584 
10585             if (params.startAngle && params.endAngle) {
10586                 this.startAngle = params.startAngle;
10587                 this.endAngle = params.endAngle;
10588                 this.x1 = params.cx + (this.radius * Math.cos(params.startAngle));
10589                 this.y1 = params.cy + (this.radius * Math.sin(params.startAngle));
10590                 this.x2 = params.cx + (this.radius * Math.cos(params.endAngle));
10591                 this.y2 = params.cy + (this.radius * Math.sin(params.endAngle));
10592             }
10593             else {
10594                 this.startAngle = _calcAngle(params.x1, params.y1);
10595                 this.endAngle = _calcAngle(params.x2, params.y2);
10596                 this.x1 = params.x1;
10597                 this.y1 = params.y1;
10598                 this.x2 = params.x2;
10599                 this.y2 = params.y2;
10600             }
10601 
10602             if (this.endAngle < 0) {
10603                 this.endAngle += TWO_PI;
10604             }
10605             if (this.startAngle < 0) {
10606                 this.startAngle += TWO_PI;
10607             }
10608 
10609             // segment is used by vml     
10610             //this.segment = _jg.quadrant([this.x1, this.y1], [this.x2, this.y2]);
10611 
10612             // we now have startAngle and endAngle as positive numbers, meaning the
10613             // absolute difference (|d|) between them is the sweep (s) of this arc, unless the
10614             // arc is 'anticlockwise' in which case 's' is given by 2PI - |d|.
10615 
10616             var ea = this.endAngle < this.startAngle ? this.endAngle + TWO_PI : this.endAngle;
10617             this.sweep = Math.abs(ea - this.startAngle);
10618             if (this.anticlockwise) {
10619                 this.sweep = TWO_PI - this.sweep;
10620             }
10621             var circumference = 2 * Math.PI * this.radius,
10622                 frac = this.sweep / TWO_PI,
10623                 length = circumference * frac;
10624 
10625             this.getLength = function () {
10626                 return length;
10627             };
10628 
10629             this.getBounds = function () {
10630                 return {
10631                     minX: params.cx - params.r,
10632                     maxX: params.cx + params.r,
10633                     minY: params.cy - params.r,
10634                     maxY: params.cy + params.r
10635                 };
10636             };
10637 
10638             var VERY_SMALL_VALUE = 0.0000000001,
10639                 gentleRound = function (n) {
10640                     var f = Math.floor(n), r = Math.ceil(n);
10641                     if (n - f < VERY_SMALL_VALUE) {
10642                         return f;
10643                     }
10644                     else if (r - n < VERY_SMALL_VALUE) {
10645                         return r;
10646                     }
10647                     return n;
10648                 };
10649 
10650             /**
10651              * returns the point on the segment's path that is 'location' along the length of the path, where 'location' is a decimal from
10652              * 0 to 1 inclusive.
10653              */
10654             this.pointOnPath = function (location, absolute) {
10655 
10656                 if (location === 0) {
10657                     return { x: this.x1, y: this.y1, theta: this.startAngle };
10658                 }
10659                 else if (location === 1) {
10660                     return { x: this.x2, y: this.y2, theta: this.endAngle };
10661                 }
10662 
10663                 if (absolute) {
10664                     location = location / length;
10665                 }
10666 
10667                 var angle = _calcAngleForLocation(this, location),
10668                     _x = params.cx + (params.r * Math.cos(angle)),
10669                     _y = params.cy + (params.r * Math.sin(angle));
10670 
10671                 return { x: gentleRound(_x), y: gentleRound(_y), theta: angle };
10672             };
10673 
10674             /**
10675              * returns the gradient of the segment at the given point.
10676              */
10677             this.gradientAtPoint = function (location, absolute) {
10678                 var p = this.pointOnPath(location, absolute);
10679                 var m = _jg.normal([ params.cx, params.cy ], [p.x, p.y ]);
10680                 if (!this.anticlockwise && (m === Infinity || m === -Infinity)) {
10681                     m *= -1;
10682                 }
10683                 return m;
10684             };
10685 
10686             this.pointAlongPathFrom = function (location, distance, absolute) {
10687                 var p = this.pointOnPath(location, absolute),
10688                     arcSpan = distance / circumference * 2 * Math.PI,
10689                     dir = this.anticlockwise ? -1 : 1,
10690                     startAngle = p.theta + (dir * arcSpan),
10691                     startX = params.cx + (this.radius * Math.cos(startAngle)),
10692                     startY = params.cy + (this.radius * Math.sin(startAngle));
10693 
10694                 return {x: startX, y: startY};
10695             };
10696         },
10697 
10698         Bezier: function (params) {
10699             this.curve = [
10700                 { x: params.x1, y: params.y1},
10701                 { x: params.cp1x, y: params.cp1y },
10702                 { x: params.cp2x, y: params.cp2y },
10703                 { x: params.x2, y: params.y2 }
10704             ];
10705 
10706             var _super = _jp.Segments.AbstractSegment.apply(this, arguments);
10707             // although this is not a strictly rigorous determination of bounds
10708             // of a bezier curve, it works for the types of curves that this segment
10709             // type produces.
10710             this.bounds = {
10711                 minX: Math.min(params.x1, params.x2, params.cp1x, params.cp2x),
10712                 minY: Math.min(params.y1, params.y2, params.cp1y, params.cp2y),
10713                 maxX: Math.max(params.x1, params.x2, params.cp1x, params.cp2x),
10714                 maxY: Math.max(params.y1, params.y2, params.cp1y, params.cp2y)
10715             };
10716 
10717             this.type = "Bezier";
10718 
10719             var _translateLocation = function (_curve, location, absolute) {
10720                 if (absolute) {
10721                     location = root.jsBezier.locationAlongCurveFrom(_curve, location > 0 ? 0 : 1, location);
10722                 }
10723 
10724                 return location;
10725             };
10726 
10727             /**
10728              * returns the point on the segment's path that is 'location' along the length of the path, where 'location' is a decimal from
10729              * 0 to 1 inclusive.
10730              */
10731             this.pointOnPath = function (location, absolute) {
10732                 location = _translateLocation(this.curve, location, absolute);
10733                 return root.jsBezier.pointOnCurve(this.curve, location);
10734             };
10735 
10736             /**
10737              * returns the gradient of the segment at the given point.
10738              */
10739             this.gradientAtPoint = function (location, absolute) {
10740                 location = _translateLocation(this.curve, location, absolute);
10741                 return root.jsBezier.gradientAtPoint(this.curve, location);
10742             };
10743 
10744             this.pointAlongPathFrom = function (location, distance, absolute) {
10745                 location = _translateLocation(this.curve, location, absolute);
10746                 return root.jsBezier.pointAlongCurveFrom(this.curve, location, distance);
10747             };
10748 
10749             this.getLength = function () {
10750                 return root.jsBezier.getLength(this.curve);
10751             };
10752 
10753             this.getBounds = function () {
10754                 return this.bounds;
10755             };
10756         }
10757     };
10758 
10759     _jp.SegmentRenderer = {
10760         getPath: function (segment) {
10761             return ({
10762                 "Straight": function () {
10763                     var d = segment.getCoordinates();
10764                     return "M " + d.x1 + " " + d.y1 + " L " + d.x2 + " " + d.y2;
10765                 },
10766                 "Bezier": function () {
10767                     var d = segment.params;
10768                     return "M " + d.x1 + " " + d.y1 +
10769                         " C " + d.cp1x + " " + d.cp1y + " " + d.cp2x + " " + d.cp2y + " " + d.x2 + " " + d.y2;
10770                 },
10771                 "Arc": function () {
10772                     var d = segment.params,
10773                         laf = segment.sweep > Math.PI ? 1 : 0,
10774                         sf = segment.anticlockwise ? 0 : 1;
10775 
10776                     return "M" + segment.x1 + " " + segment.y1 + " A " + segment.radius + " " + d.r + " 0 " + laf + "," + sf + " " + segment.x2 + " " + segment.y2;
10777                 }
10778             })[segment.type]();
10779         }
10780     };
10781 
10782     /*
10783      Class: AbstractComponent
10784      Superclass for AbstractConnector and AbstractEndpoint.
10785      */
10786     var AbstractComponent = function () {
10787         this.resetBounds = function () {
10788             this.bounds = { minX: Infinity, minY: Infinity, maxX: -Infinity, maxY: -Infinity };
10789         };
10790         this.resetBounds();
10791     };
10792 
10793     /*
10794      * Class: AbstractConnector
10795      * Superclass for all Connectors; here is where Segments are managed.  This is exposed on jsPlumb just so it
10796      * can be accessed from other files. You should not try to instantiate one of these directly.
10797      *
10798      * When this class is asked for a pointOnPath, or gradient etc, it must first figure out which segment to dispatch
10799      * that request to. This is done by keeping track of the total connector length as segments are added, and also
10800      * their cumulative ratios to the total length.  Then when the right segment is found it is a simple case of dispatching
10801      * the request to it (and adjusting 'location' so that it is relative to the beginning of that segment.)
10802      */
10803     _jp.Connectors.AbstractConnector = function (params) {
10804 
10805         AbstractComponent.apply(this, arguments);
10806 
10807         var segments = [],
10808             totalLength = 0,
10809             segmentProportions = [],
10810             segmentProportionalLengths = [],
10811             stub = params.stub || 0,
10812             sourceStub = _ju.isArray(stub) ? stub[0] : stub,
10813             targetStub = _ju.isArray(stub) ? stub[1] : stub,
10814             gap = params.gap || 0,
10815             sourceGap = _ju.isArray(gap) ? gap[0] : gap,
10816             targetGap = _ju.isArray(gap) ? gap[1] : gap,
10817             userProvidedSegments = null,
10818             edited = false,
10819             paintInfo = null,
10820             geometry = null,
10821             editable = params.editable !== false && _jp.ConnectorEditors != null && _jp.ConnectorEditors[this.type] != null;
10822 
10823         var _setGeometry = this.setGeometry = function(g, internallyComputed) {
10824             edited = (!internallyComputed);
10825             geometry = g;
10826         };
10827         var _getGeometry = this.getGeometry = function() {
10828             return geometry;
10829         };
10830 
10831         this.getPathData = function() {
10832             var p = "";
10833             for (var i = 0; i < segments.length; i++) {
10834                 p += _jp.SegmentRenderer.getPath(segments[i]);
10835                 p += " ";
10836             }
10837             return p;
10838         };
10839 
10840         this.hasBeenEdited = function() { return edited; };
10841         this.isEditing = function() { return this.editor != null && this.editor.isActive(); };
10842         this.setEditable = function(e) {
10843             // if this connector has an editor already, or
10844             // if an editor for this connector's type is available, or
10845             // if the child declares an overrideSetEditable and it does not return false, editable is true.
10846             if (e && _jp.ConnectorEditors != null && _jp.ConnectorEditors[this.type] != null && (this.overrideSetEditable == null || this.overrideSetEditable())) {
10847                 editable = e;
10848             } else {
10849                 editable = false;
10850             }
10851             return editable;
10852         };
10853         this.isEditable = function() { return editable; };
10854 
10855         /**
10856          * Function: findSegmentForPoint
10857          * Returns the segment that is closest to the given [x,y],
10858          * null if nothing found.  This function returns a JS
10859          * object with:
10860          *
10861          *   d   -   distance from segment
10862          *   l   -   proportional location in segment
10863          *   x   -   x point on the segment
10864          *   y   -   y point on the segment
10865          *   s   -   the segment itself.
10866          */
10867         this.findSegmentForPoint = function (x, y) {
10868             var out = { d: Infinity, s: null, x: null, y: null, l: null };
10869             for (var i = 0; i < segments.length; i++) {
10870                 var _s = segments[i].findClosestPointOnPath(x, y);
10871                 if (_s.d < out.d) {
10872                     out.d = _s.d;
10873                     out.l = _s.l;
10874                     out.x = _s.x;
10875                     out.y = _s.y;
10876                     out.s = segments[i];
10877                     out.x1 = _s.x1;
10878                     out.x2 = _s.x2;
10879                     out.y1 = _s.y1;
10880                     out.y2 = _s.y2;
10881                     out.index = i;
10882                 }
10883             }
10884 
10885             return out;
10886         };
10887 
10888         var _updateSegmentProportions = function () {
10889                 var curLoc = 0;
10890                 for (var i = 0; i < segments.length; i++) {
10891                     var sl = segments[i].getLength();
10892                     segmentProportionalLengths[i] = sl / totalLength;
10893                     segmentProportions[i] = [curLoc, (curLoc += (sl / totalLength)) ];
10894                 }
10895             },
10896 
10897             /**
10898              * returns [segment, proportion of travel in segment, segment index] for the segment
10899              * that contains the point which is 'location' distance along the entire path, where
10900              * 'location' is a decimal between 0 and 1 inclusive. in this connector type, paths
10901              * are made up of a list of segments, each of which contributes some fraction to
10902              * the total length.
10903              * From 1.3.10 this also supports the 'absolute' property, which lets us specify a location
10904              * as the absolute distance in pixels, rather than a proportion of the total path.
10905              */
10906             _findSegmentForLocation = function (location, absolute) {
10907                 if (absolute) {
10908                     location = location > 0 ? location / totalLength : (totalLength + location) / totalLength;
10909                 }
10910                 var idx = segmentProportions.length - 1, inSegmentProportion = 1;
10911                 for (var i = 0; i < segmentProportions.length; i++) {
10912                     if (segmentProportions[i][1] >= location) {
10913                         idx = i;
10914                         // todo is this correct for all connector path types?
10915                         inSegmentProportion = location === 1 ? 1 : location === 0 ? 0 : (location - segmentProportions[i][0]) / segmentProportionalLengths[i];
10916                         break;
10917                     }
10918                 }
10919                 return { segment: segments[idx], proportion: inSegmentProportion, index: idx };
10920             },
10921             _addSegment = function (conn, type, params) {
10922                 if (params.x1 === params.x2 && params.y1 === params.y2) {
10923                     return;
10924                 }
10925                 var s = new _jp.Segments[type](params);
10926                 segments.push(s);
10927                 totalLength += s.getLength();
10928                 conn.updateBounds(s);
10929             },
10930             _clearSegments = function () {
10931                 totalLength = segments.length = segmentProportions.length = segmentProportionalLengths.length = 0;
10932             };
10933 
10934         this.setSegments = function (_segs) {
10935             userProvidedSegments = [];
10936             totalLength = 0;
10937             for (var i = 0; i < _segs.length; i++) {
10938                 userProvidedSegments.push(_segs[i]);
10939                 totalLength += _segs[i].getLength();
10940             }
10941         };
10942 
10943         this.getLength = function() {
10944             return totalLength;
10945         };
10946 
10947         var _prepareCompute = function (params) {
10948             this.strokeWidth = params.strokeWidth;
10949             var segment = _jg.quadrant(params.sourcePos, params.targetPos),
10950                 swapX = params.targetPos[0] < params.sourcePos[0],
10951                 swapY = params.targetPos[1] < params.sourcePos[1],
10952                 lw = params.strokeWidth || 1,
10953                 so = params.sourceEndpoint.anchor.getOrientation(params.sourceEndpoint),
10954                 to = params.targetEndpoint.anchor.getOrientation(params.targetEndpoint),
10955                 x = swapX ? params.targetPos[0] : params.sourcePos[0],
10956                 y = swapY ? params.targetPos[1] : params.sourcePos[1],
10957                 w = Math.abs(params.targetPos[0] - params.sourcePos[0]),
10958                 h = Math.abs(params.targetPos[1] - params.sourcePos[1]);
10959 
10960             // if either anchor does not have an orientation set, we derive one from their relative
10961             // positions.  we fix the axis to be the one in which the two elements are further apart, and
10962             // point each anchor at the other element.  this is also used when dragging a new connection.
10963             if (so[0] === 0 && so[1] === 0 || to[0] === 0 && to[1] === 0) {
10964                 var index = w > h ? 0 : 1, oIndex = [1, 0][index];
10965                 so = [];
10966                 to = [];
10967                 so[index] = params.sourcePos[index] > params.targetPos[index] ? -1 : 1;
10968                 to[index] = params.sourcePos[index] > params.targetPos[index] ? 1 : -1;
10969                 so[oIndex] = 0;
10970                 to[oIndex] = 0;
10971             }
10972 
10973             var sx = swapX ? w + (sourceGap * so[0]) : sourceGap * so[0],
10974                 sy = swapY ? h + (sourceGap * so[1]) : sourceGap * so[1],
10975                 tx = swapX ? targetGap * to[0] : w + (targetGap * to[0]),
10976                 ty = swapY ? targetGap * to[1] : h + (targetGap * to[1]),
10977                 oProduct = ((so[0] * to[0]) + (so[1] * to[1]));
10978 
10979             var result = {
10980                 sx: sx, sy: sy, tx: tx, ty: ty, lw: lw,
10981                 xSpan: Math.abs(tx - sx),
10982                 ySpan: Math.abs(ty - sy),
10983                 mx: (sx + tx) / 2,
10984                 my: (sy + ty) / 2,
10985                 so: so, to: to, x: x, y: y, w: w, h: h,
10986                 segment: segment,
10987                 startStubX: sx + (so[0] * sourceStub),
10988                 startStubY: sy + (so[1] * sourceStub),
10989                 endStubX: tx + (to[0] * targetStub),
10990                 endStubY: ty + (to[1] * targetStub),
10991                 isXGreaterThanStubTimes2: Math.abs(sx - tx) > (sourceStub + targetStub),
10992                 isYGreaterThanStubTimes2: Math.abs(sy - ty) > (sourceStub + targetStub),
10993                 opposite: oProduct === -1,
10994                 perpendicular: oProduct === 0,
10995                 orthogonal: oProduct === 1,
10996                 sourceAxis: so[0] === 0 ? "y" : "x",
10997                 points: [x, y, w, h, sx, sy, tx, ty ]
10998             };
10999             result.anchorOrientation = result.opposite ? "opposite" : result.orthogonal ? "orthogonal" : "perpendicular";
11000             return result;
11001         };
11002 
11003         this.getSegments = function () {
11004             return segments;
11005         };
11006 
11007         this.updateBounds = function (segment) {
11008             var segBounds = segment.getBounds();
11009             this.bounds.minX = Math.min(this.bounds.minX, segBounds.minX);
11010             this.bounds.maxX = Math.max(this.bounds.maxX, segBounds.maxX);
11011             this.bounds.minY = Math.min(this.bounds.minY, segBounds.minY);
11012             this.bounds.maxY = Math.max(this.bounds.maxY, segBounds.maxY);
11013         };
11014 
11015         var dumpSegmentsToConsole = function () {
11016             console.log("SEGMENTS:");
11017             for (var i = 0; i < segments.length; i++) {
11018                 console.log(segments[i].type, segments[i].getLength(), segmentProportions[i]);
11019             }
11020         };
11021 
11022         this.pointOnPath = function (location, absolute) {
11023             var seg = _findSegmentForLocation(location, absolute);
11024             return seg.segment && seg.segment.pointOnPath(seg.proportion, false) || [0, 0];
11025         };
11026 
11027         this.gradientAtPoint = function (location, absolute) {
11028             var seg = _findSegmentForLocation(location, absolute);
11029             return seg.segment && seg.segment.gradientAtPoint(seg.proportion, false) || 0;
11030         };
11031 
11032         this.pointAlongPathFrom = function (location, distance, absolute) {
11033             var seg = _findSegmentForLocation(location, absolute);
11034             // TODO what happens if this crosses to the next segment?
11035             return seg.segment && seg.segment.pointAlongPathFrom(seg.proportion, distance, false) || [0, 0];
11036         };
11037 
11038         this.compute = function (params) {
11039             paintInfo = _prepareCompute.call(this, params);
11040 
11041             _clearSegments();
11042             this._compute(paintInfo, params);
11043             this.x = paintInfo.points[0];
11044             this.y = paintInfo.points[1];
11045             this.w = paintInfo.points[2];
11046             this.h = paintInfo.points[3];
11047             this.segment = paintInfo.segment;
11048             _updateSegmentProportions();
11049         };
11050 
11051         return {
11052             addSegment: _addSegment,
11053             prepareCompute: _prepareCompute,
11054             sourceStub: sourceStub,
11055             targetStub: targetStub,
11056             maxStub: Math.max(sourceStub, targetStub),
11057             sourceGap: sourceGap,
11058             targetGap: targetGap,
11059             maxGap: Math.max(sourceGap, targetGap),
11060             setGeometry:_setGeometry,
11061             getGeometry:_getGeometry
11062         };
11063     };
11064     _ju.extend(_jp.Connectors.AbstractConnector, AbstractComponent);
11065 
11066 
11067     // ********************************* END OF CONNECTOR TYPES *******************************************************************
11068 
11069     // ********************************* ENDPOINT TYPES *******************************************************************
11070 
11071     _jp.Endpoints.AbstractEndpoint = function (params) {
11072         AbstractComponent.apply(this, arguments);
11073         var compute = this.compute = function (anchorPoint, orientation, endpointStyle, connectorPaintStyle) {
11074             var out = this._compute.apply(this, arguments);
11075             this.x = out[0];
11076             this.y = out[1];
11077             this.w = out[2];
11078             this.h = out[3];
11079             this.bounds.minX = this.x;
11080             this.bounds.minY = this.y;
11081             this.bounds.maxX = this.x + this.w;
11082             this.bounds.maxY = this.y + this.h;
11083             return out;
11084         };
11085         return {
11086             compute: compute,
11087             cssClass: params.cssClass
11088         };
11089     };
11090     _ju.extend(_jp.Endpoints.AbstractEndpoint, AbstractComponent);
11091 
11092     /**
11093      * Class: Endpoints.Dot
11094      * A round endpoint, with default radius 10 pixels.
11095      */
11096 
11097     /**
11098      * Function: Constructor
11099      *
11100      * Parameters:
11101      *
11102      *    radius    -    radius of the endpoint.  defaults to 10 pixels.
11103      */
11104     _jp.Endpoints.Dot = function (params) {
11105         this.type = "Dot";
11106         var _super = _jp.Endpoints.AbstractEndpoint.apply(this, arguments);
11107         params = params || {};
11108         this.radius = params.radius || 10;
11109         this.defaultOffset = 0.5 * this.radius;
11110         this.defaultInnerRadius = this.radius / 3;
11111 
11112         this._compute = function (anchorPoint, orientation, endpointStyle, connectorPaintStyle) {
11113             this.radius = endpointStyle.radius || this.radius;
11114             var x = anchorPoint[0] - this.radius,
11115                 y = anchorPoint[1] - this.radius,
11116                 w = this.radius * 2,
11117                 h = this.radius * 2;
11118 
11119             if (endpointStyle.stroke) {
11120                 var lw = endpointStyle.strokeWidth || 1;
11121                 x -= lw;
11122                 y -= lw;
11123                 w += (lw * 2);
11124                 h += (lw * 2);
11125             }
11126             return [ x, y, w, h, this.radius ];
11127         };
11128     };
11129     _ju.extend(_jp.Endpoints.Dot, _jp.Endpoints.AbstractEndpoint);
11130 
11131     _jp.Endpoints.Rectangle = function (params) {
11132         this.type = "Rectangle";
11133         var _super = _jp.Endpoints.AbstractEndpoint.apply(this, arguments);
11134         params = params || {};
11135         this.width = params.width || 20;
11136         this.height = params.height || 20;
11137 
11138         this._compute = function (anchorPoint, orientation, endpointStyle, connectorPaintStyle) {
11139             var width = endpointStyle.width || this.width,
11140                 height = endpointStyle.height || this.height,
11141                 x = anchorPoint[0] - (width / 2),
11142                 y = anchorPoint[1] - (height / 2);
11143 
11144             return [ x, y, width, height];
11145         };
11146     };
11147     _ju.extend(_jp.Endpoints.Rectangle, _jp.Endpoints.AbstractEndpoint);
11148 
11149     var DOMElementEndpoint = function (params) {
11150         _jp.jsPlumbUIComponent.apply(this, arguments);
11151         this._jsPlumb.displayElements = [];
11152     };
11153     _ju.extend(DOMElementEndpoint, _jp.jsPlumbUIComponent, {
11154         getDisplayElements: function () {
11155             return this._jsPlumb.displayElements;
11156         },
11157         appendDisplayElement: function (el) {
11158             this._jsPlumb.displayElements.push(el);
11159         }
11160     });
11161 
11162     /**
11163      * Class: Endpoints.Image
11164      * Draws an image as the Endpoint.
11165      */
11166     /**
11167      * Function: Constructor
11168      *
11169      * Parameters:
11170      *
11171      *    src    -    location of the image to use.
11172 
11173      TODO: multiple references to self. not sure quite how to get rid of them entirely. perhaps self = null in the cleanup
11174      function will suffice
11175 
11176      TODO this class still might leak memory.
11177 
11178      */
11179     _jp.Endpoints.Image = function (params) {
11180 
11181         this.type = "Image";
11182         DOMElementEndpoint.apply(this, arguments);
11183         _jp.Endpoints.AbstractEndpoint.apply(this, arguments);
11184 
11185         var _onload = params.onload,
11186             src = params.src || params.url,
11187             clazz = params.cssClass ? " " + params.cssClass : "";
11188 
11189         this._jsPlumb.img = new Image();
11190         this._jsPlumb.ready = false;
11191         this._jsPlumb.initialized = false;
11192         this._jsPlumb.deleted = false;
11193         this._jsPlumb.widthToUse = params.width;
11194         this._jsPlumb.heightToUse = params.height;
11195         this._jsPlumb.endpoint = params.endpoint;
11196 
11197         this._jsPlumb.img.onload = function () {
11198             if (this._jsPlumb != null) {
11199                 this._jsPlumb.ready = true;
11200                 this._jsPlumb.widthToUse = this._jsPlumb.widthToUse || this._jsPlumb.img.width;
11201                 this._jsPlumb.heightToUse = this._jsPlumb.heightToUse || this._jsPlumb.img.height;
11202                 if (_onload) {
11203                     _onload(this);
11204                 }
11205             }
11206         }.bind(this);
11207 
11208         /*
11209          Function: setImage
11210          Sets the Image to use in this Endpoint.
11211 
11212          Parameters:
11213          img         -   may be a URL or an Image object
11214          onload      -   optional; a callback to execute once the image has loaded.
11215          */
11216         this._jsPlumb.endpoint.setImage = function (_img, onload) {
11217             var s = _img.constructor === String ? _img : _img.src;
11218             _onload = onload;
11219             this._jsPlumb.img.src = s;
11220 
11221             if (this.canvas != null) {
11222                 this.canvas.setAttribute("src", this._jsPlumb.img.src);
11223             }
11224         }.bind(this);
11225 
11226         this._jsPlumb.endpoint.setImage(src, _onload);
11227         this._compute = function (anchorPoint, orientation, endpointStyle, connectorPaintStyle) {
11228             this.anchorPoint = anchorPoint;
11229             if (this._jsPlumb.ready) {
11230                 return [anchorPoint[0] - this._jsPlumb.widthToUse / 2, anchorPoint[1] - this._jsPlumb.heightToUse / 2,
11231                     this._jsPlumb.widthToUse, this._jsPlumb.heightToUse];
11232             }
11233             else {
11234                 return [0, 0, 0, 0];
11235             }
11236         };
11237 
11238         this.canvas = _jp.createElement("img", {
11239             position:"absolute",
11240             margin:0,
11241             padding:0,
11242             outline:0
11243         }, this._jsPlumb.instance.endpointClass + clazz);
11244 
11245         if (this._jsPlumb.widthToUse) {
11246             this.canvas.setAttribute("width", this._jsPlumb.widthToUse);
11247         }
11248         if (this._jsPlumb.heightToUse) {
11249             this.canvas.setAttribute("height", this._jsPlumb.heightToUse);
11250         }
11251         this._jsPlumb.instance.appendElement(this.canvas);
11252 
11253         this.actuallyPaint = function (d, style, anchor) {
11254             if (!this._jsPlumb.deleted) {
11255                 if (!this._jsPlumb.initialized) {
11256                     this.canvas.setAttribute("src", this._jsPlumb.img.src);
11257                     this.appendDisplayElement(this.canvas);
11258                     this._jsPlumb.initialized = true;
11259                 }
11260                 var x = this.anchorPoint[0] - (this._jsPlumb.widthToUse / 2),
11261                     y = this.anchorPoint[1] - (this._jsPlumb.heightToUse / 2);
11262                 _ju.sizeElement(this.canvas, x, y, this._jsPlumb.widthToUse, this._jsPlumb.heightToUse);
11263             }
11264         };
11265 
11266         this.paint = function (style, anchor) {
11267             if (this._jsPlumb != null) {  // may have been deleted
11268                 if (this._jsPlumb.ready) {
11269                     this.actuallyPaint(style, anchor);
11270                 }
11271                 else {
11272                     root.setTimeout(function () {
11273                         this.paint(style, anchor);
11274                     }.bind(this), 200);
11275                 }
11276             }
11277         };
11278     };
11279     _ju.extend(_jp.Endpoints.Image, [ DOMElementEndpoint, _jp.Endpoints.AbstractEndpoint ], {
11280         cleanup: function (force) {
11281             if (force) {
11282                 this._jsPlumb.deleted = true;
11283                 if (this.canvas) {
11284                     this.canvas.parentNode.removeChild(this.canvas);
11285                 }
11286                 this.canvas = null;
11287             }
11288         }
11289     });
11290 
11291     /*
11292      * Class: Endpoints.Blank
11293      * An Endpoint that paints nothing (visible) on the screen.  Supports cssClass and hoverClass parameters like all Endpoints.
11294      */
11295     _jp.Endpoints.Blank = function (params) {
11296         var _super = _jp.Endpoints.AbstractEndpoint.apply(this, arguments);
11297         this.type = "Blank";
11298         DOMElementEndpoint.apply(this, arguments);
11299         this._compute = function (anchorPoint, orientation, endpointStyle, connectorPaintStyle) {
11300             return [anchorPoint[0], anchorPoint[1], 10, 0];
11301         };
11302 
11303         var clazz = params.cssClass ? " " + params.cssClass : "";
11304 
11305         this.canvas = _jp.createElement("div", {
11306             display: "block",
11307             width: "1px",
11308             height: "1px",
11309             background: "transparent",
11310             position: "absolute"
11311         }, this._jsPlumb.instance.endpointClass + clazz);
11312 
11313         this._jsPlumb.instance.appendElement(this.canvas);
11314 
11315         this.paint = function (style, anchor) {
11316             _ju.sizeElement(this.canvas, this.x, this.y, this.w, this.h);
11317         };
11318     };
11319     _ju.extend(_jp.Endpoints.Blank, [_jp.Endpoints.AbstractEndpoint, DOMElementEndpoint], {
11320         cleanup: function () {
11321             if (this.canvas && this.canvas.parentNode) {
11322                 this.canvas.parentNode.removeChild(this.canvas);
11323             }
11324         }
11325     });
11326 
11327     /*
11328      * Class: Endpoints.Triangle
11329      * A triangular Endpoint.
11330      */
11331     /*
11332      * Function: Constructor
11333      *
11334      * Parameters:
11335      *
11336      * width   width of the triangle's base.  defaults to 55 pixels.
11337      * height  height of the triangle from base to apex.  defaults to 55 pixels.
11338      */
11339     _jp.Endpoints.Triangle = function (params) {
11340         this.type = "Triangle";
11341         _jp.Endpoints.AbstractEndpoint.apply(this, arguments);
11342         var self = this;
11343         params = params || {  };
11344         params.width = params.width || 55;
11345         params.height = params.height || 55;
11346         this.width = params.width;
11347         this.height = params.height;
11348         this._compute = function (anchorPoint, orientation, endpointStyle, connectorPaintStyle) {
11349             var width = endpointStyle.width || self.width,
11350                 height = endpointStyle.height || self.height,
11351                 x = anchorPoint[0] - (width / 2),
11352                 y = anchorPoint[1] - (height / 2);
11353             return [ x, y, width, height ];
11354         };
11355     };
11356 // ********************************* END OF ENDPOINT TYPES *******************************************************************
11357 
11358 
11359 // ********************************* OVERLAY DEFINITIONS ***********************************************************************    
11360 
11361     var AbstractOverlay = _jp.Overlays.AbstractOverlay = function (params) {
11362         this.visible = true;
11363         this.isAppendedAtTopLevel = true;
11364         this.component = params.component;
11365         this.loc = params.location == null ? 0.5 : params.location;
11366         this.endpointLoc = params.endpointLocation == null ? [ 0.5, 0.5] : params.endpointLocation;
11367         this.visible = params.visible !== false;
11368     };
11369     AbstractOverlay.prototype = {
11370         cleanup: function (force) {
11371             if (force) {
11372                 this.component = null;
11373                 this.canvas = null;
11374                 this.endpointLoc = null;
11375             }
11376         },
11377         reattach:function(instance) {
11378 
11379         },
11380         setVisible: function (val) {
11381             this.visible = val;
11382             this.component.repaint();
11383         },
11384         isVisible: function () {
11385             return this.visible;
11386         },
11387         hide: function () {
11388             this.setVisible(false);
11389         },
11390         show: function () {
11391             this.setVisible(true);
11392         },
11393         incrementLocation: function (amount) {
11394             this.loc += amount;
11395             this.component.repaint();
11396         },
11397         setLocation: function (l) {
11398             this.loc = l;
11399             this.component.repaint();
11400         },
11401         getLocation: function () {
11402             return this.loc;
11403         },
11404         updateFrom:function() { }
11405     };
11406 
11407 
11408     /*
11409      * Class: Overlays.Arrow
11410      *
11411      * An arrow overlay, defined by four points: the head, the two sides of the tail, and a 'foldback' point at some distance along the length
11412      * of the arrow that lines from each tail point converge into.  The foldback point is defined using a decimal that indicates some fraction
11413      * of the length of the arrow and has a default value of 0.623.  A foldback point value of 1 would mean that the arrow had a straight line
11414      * across the tail.
11415      */
11416     /*
11417      * @constructor
11418      *
11419      * @param {Object} params Constructor params.
11420      * @param {Number} [params.length] Distance in pixels from head to tail baseline. default 20.
11421      * @param {Number} [params.width] Width in pixels of the tail baseline. default 20.
11422      * @param {String} [params.fill] Style to use when filling the arrow.  defaults to "black".
11423      * @param {String} [params.stroke] Style to use when stroking the arrow. defaults to null, which means the arrow is not stroked.
11424      * @param {Number} [params.stroke-width] Line width to use when stroking the arrow. defaults to 1, but only used if stroke is not null.
11425      * @param {Number} [params.foldback] Distance (as a decimal from 0 to 1 inclusive) along the length of the arrow marking the point the tail points should fold back to.  defaults to 0.623.
11426      * @param {Number} [params.location] Distance (as a decimal from 0 to 1 inclusive) marking where the arrow should sit on the connector. defaults to 0.5.
11427      * @param {NUmber} [params.direction] Indicates the direction the arrow points in. valid values are -1 and 1; 1 is default.
11428      */
11429     _jp.Overlays.Arrow = function (params) {
11430         this.type = "Arrow";
11431         AbstractOverlay.apply(this, arguments);
11432         this.isAppendedAtTopLevel = false;
11433         params = params || {};
11434         var self = this;
11435 
11436         this.length = params.length || 20;
11437         this.width = params.width || 20;
11438         this.id = params.id;
11439         var direction = (params.direction || 1) < 0 ? -1 : 1,
11440             paintStyle = params.paintStyle || { "stroke-width": 1 },
11441         // how far along the arrow the lines folding back in come to. default is 62.3%.
11442             foldback = params.foldback || 0.623;
11443 
11444         this.computeMaxSize = function () {
11445             return self.width * 1.5;
11446         };
11447 
11448         this.elementCreated = function(p, component) {
11449             this.path = p;
11450             if (params.events) {
11451                 for (var i in params.events) {
11452                     _jp.on(p, i, params.events[i]);
11453                 }
11454             }
11455         };
11456 
11457         this.draw = function (component, currentConnectionPaintStyle) {
11458 
11459             var hxy, mid, txy, tail, cxy;
11460             if (component.pointAlongPathFrom) {
11461 
11462                 if (_ju.isString(this.loc) || this.loc > 1 || this.loc < 0) {
11463                     var l = parseInt(this.loc, 10),
11464                         fromLoc = this.loc < 0 ? 1 : 0;
11465                     hxy = component.pointAlongPathFrom(fromLoc, l, false);
11466                     mid = component.pointAlongPathFrom(fromLoc, l - (direction * this.length / 2), false);
11467                     txy = _jg.pointOnLine(hxy, mid, this.length);
11468                 }
11469                 else if (this.loc === 1) {
11470                     hxy = component.pointOnPath(this.loc);
11471                     mid = component.pointAlongPathFrom(this.loc, -(this.length));
11472                     txy = _jg.pointOnLine(hxy, mid, this.length);
11473 
11474                     if (direction === -1) {
11475                         var _ = txy;
11476                         txy = hxy;
11477                         hxy = _;
11478                     }
11479                 }
11480                 else if (this.loc === 0) {
11481                     txy = component.pointOnPath(this.loc);
11482                     mid = component.pointAlongPathFrom(this.loc, this.length);
11483                     hxy = _jg.pointOnLine(txy, mid, this.length);
11484                     if (direction === -1) {
11485                         var __ = txy;
11486                         txy = hxy;
11487                         hxy = __;
11488                     }
11489                 }
11490                 else {
11491                     hxy = component.pointAlongPathFrom(this.loc, direction * this.length / 2);
11492                     mid = component.pointOnPath(this.loc);
11493                     txy = _jg.pointOnLine(hxy, mid, this.length);
11494                 }
11495 
11496                 tail = _jg.perpendicularLineTo(hxy, txy, this.width);
11497                 cxy = _jg.pointOnLine(hxy, txy, foldback * this.length);
11498 
11499                 var d = { hxy: hxy, tail: tail, cxy: cxy },
11500                     stroke = paintStyle.stroke || currentConnectionPaintStyle.stroke,
11501                     fill = paintStyle.fill || currentConnectionPaintStyle.stroke,
11502                     lineWidth = paintStyle.strokeWidth || currentConnectionPaintStyle.strokeWidth;
11503 
11504                 return {
11505                     component: component,
11506                     d: d,
11507                     "stroke-width": lineWidth,
11508                     stroke: stroke,
11509                     fill: fill,
11510                     minX: Math.min(hxy.x, tail[0].x, tail[1].x),
11511                     maxX: Math.max(hxy.x, tail[0].x, tail[1].x),
11512                     minY: Math.min(hxy.y, tail[0].y, tail[1].y),
11513                     maxY: Math.max(hxy.y, tail[0].y, tail[1].y)
11514                 };
11515             }
11516             else {
11517                 return {component: component, minX: 0, maxX: 0, minY: 0, maxY: 0};
11518             }
11519         };
11520     };
11521     _ju.extend(_jp.Overlays.Arrow, AbstractOverlay, {
11522         updateFrom:function(d) {
11523             this.length = d.length || this.length;
11524             this.width = d.width|| this.width;
11525             this.direction = d.direction != null ? d.direction : this.direction;
11526             this.foldback = d.foldback|| this.foldback;
11527         }
11528     });
11529 
11530     /*
11531      * Class: Overlays.PlainArrow
11532      *
11533      * A basic arrow.  This is in fact just one instance of the more generic case in which the tail folds back on itself to some
11534      * point along the length of the arrow: in this case, that foldback point is the full length of the arrow.  so it just does
11535      * a 'call' to Arrow with foldback set appropriately.
11536      */
11537     /*
11538      * Function: Constructor
11539      * See <Overlays.Arrow> for allowed parameters for this overlay.
11540      */
11541     _jp.Overlays.PlainArrow = function (params) {
11542         params = params || {};
11543         var p = _jp.extend(params, {foldback: 1});
11544         _jp.Overlays.Arrow.call(this, p);
11545         this.type = "PlainArrow";
11546     };
11547     _ju.extend(_jp.Overlays.PlainArrow, _jp.Overlays.Arrow);
11548 
11549     /*
11550      * Class: Overlays.Diamond
11551      * 
11552      * A diamond. Like PlainArrow, this is a concrete case of the more generic case of the tail points converging on some point...it just
11553      * happens that in this case, that point is greater than the length of the the arrow.
11554      *
11555      *      this could probably do with some help with positioning...due to the way it reuses the Arrow paint code, what Arrow thinks is the
11556      *      center is actually 1/4 of the way along for this guy.  but we don't have any knowledge of pixels at this point, so we're kind of
11557      *      stuck when it comes to helping out the Arrow class. possibly we could pass in a 'transpose' parameter or something. the value
11558      *      would be -l/4 in this case - move along one quarter of the total length.
11559      */
11560     /*
11561      * Function: Constructor
11562      * See <Overlays.Arrow> for allowed parameters for this overlay.
11563      */
11564     _jp.Overlays.Diamond = function (params) {
11565         params = params || {};
11566         var l = params.length || 40,
11567             p = _jp.extend(params, {length: l / 2, foldback: 2});
11568         _jp.Overlays.Arrow.call(this, p);
11569         this.type = "Diamond";
11570     };
11571     _ju.extend(_jp.Overlays.Diamond, _jp.Overlays.Arrow);
11572 
11573     var _getDimensions = function (component, forceRefresh) {
11574         if (component._jsPlumb.cachedDimensions == null || forceRefresh) {
11575             component._jsPlumb.cachedDimensions = component.getDimensions();
11576         }
11577         return component._jsPlumb.cachedDimensions;
11578     };
11579 
11580     // abstract superclass for overlays that add an element to the DOM.
11581     var AbstractDOMOverlay = function (params) {
11582         _jp.jsPlumbUIComponent.apply(this, arguments);
11583         AbstractOverlay.apply(this, arguments);
11584 
11585         // hand off fired events to associated component.
11586         var _f = this.fire;
11587         this.fire = function () {
11588             _f.apply(this, arguments);
11589             if (this.component) {
11590                 this.component.fire.apply(this.component, arguments);
11591             }
11592         };
11593 
11594         this.detached=false;
11595         this.id = params.id;
11596         this._jsPlumb.div = null;
11597         this._jsPlumb.initialised = false;
11598         this._jsPlumb.component = params.component;
11599         this._jsPlumb.cachedDimensions = null;
11600         this._jsPlumb.create = params.create;
11601         this._jsPlumb.initiallyInvisible = params.visible === false;
11602 
11603         this.getElement = function () {
11604             if (this._jsPlumb.div == null) {
11605                 var div = this._jsPlumb.div = _jp.getElement(this._jsPlumb.create(this._jsPlumb.component));
11606                 div.style.position = "absolute";
11607                 div.className = this._jsPlumb.instance.overlayClass + " " +
11608                     (this.cssClass ? this.cssClass :
11609                         params.cssClass ? params.cssClass : "");
11610                 this._jsPlumb.instance.appendElement(div);
11611                 this._jsPlumb.instance.getId(div);
11612                 this.canvas = div;
11613 
11614                 // in IE the top left corner is what it placed at the desired location.  This will not
11615                 // be fixed. IE8 is not going to be supported for much longer.
11616                 var ts = "translate(-50%, -50%)";
11617                 div.style.webkitTransform = ts;
11618                 div.style.mozTransform = ts;
11619                 div.style.msTransform = ts;
11620                 div.style.oTransform = ts;
11621                 div.style.transform = ts;
11622 
11623                 // write the related component into the created element
11624                 div._jsPlumb = this;
11625 
11626                 if (params.visible === false) {
11627                     div.style.display = "none";
11628                 }
11629             }
11630             return this._jsPlumb.div;
11631         };
11632 
11633         this.draw = function (component, currentConnectionPaintStyle, absolutePosition) {
11634             var td = _getDimensions(this);
11635             if (td != null && td.length === 2) {
11636                 var cxy = { x: 0, y: 0 };
11637 
11638                 // absolutePosition would have been set by a call to connection.setAbsoluteOverlayPosition.
11639                 if (absolutePosition) {
11640                     cxy = { x: absolutePosition[0], y: absolutePosition[1] };
11641                 }
11642                 else if (component.pointOnPath) {
11643                     var loc = this.loc, absolute = false;
11644                     if (_ju.isString(this.loc) || this.loc < 0 || this.loc > 1) {
11645                         loc = parseInt(this.loc, 10);
11646                         absolute = true;
11647                     }
11648                     cxy = component.pointOnPath(loc, absolute);  // a connection
11649                 }
11650                 else {
11651                     var locToUse = this.loc.constructor === Array ? this.loc : this.endpointLoc;
11652                     cxy = { x: locToUse[0] * component.w,
11653                         y: locToUse[1] * component.h };
11654                 }
11655 
11656                 var minx = cxy.x - (td[0] / 2),
11657                     miny = cxy.y - (td[1] / 2);
11658 
11659                 return {
11660                     component: component,
11661                     d: { minx: minx, miny: miny, td: td, cxy: cxy },
11662                     minX: minx,
11663                     maxX: minx + td[0],
11664                     minY: miny,
11665                     maxY: miny + td[1]
11666                 };
11667             }
11668             else {
11669                 return {minX: 0, maxX: 0, minY: 0, maxY: 0};
11670             }
11671         };
11672     };
11673     _ju.extend(AbstractDOMOverlay, [_jp.jsPlumbUIComponent, AbstractOverlay], {
11674         getDimensions: function () {
11675             return [1,1];
11676         },
11677         setVisible: function (state) {
11678             if (this._jsPlumb.div) {
11679                 this._jsPlumb.div.style.display = state ? "block" : "none";
11680                 // if initially invisible, dimensions are 0,0 and never get updated
11681                 if (state && this._jsPlumb.initiallyInvisible) {
11682                     _getDimensions(this, true);
11683                     this.component.repaint();
11684                     this._jsPlumb.initiallyInvisible = false;
11685                 }
11686             }
11687         },
11688         /*
11689          * Function: clearCachedDimensions
11690          * Clears the cached dimensions for the label. As a performance enhancement, label dimensions are
11691          * cached from 1.3.12 onwards. The cache is cleared when you change the label text, of course, but
11692          * there are other reasons why the text dimensions might change - if you make a change through CSS, for
11693          * example, you might change the font size.  in that case you should explicitly call this method.
11694          */
11695         clearCachedDimensions: function () {
11696             this._jsPlumb.cachedDimensions = null;
11697         },
11698         cleanup: function (force) {
11699             if (force) {
11700                 if (this._jsPlumb.div != null) {
11701                     this._jsPlumb.div._jsPlumb = null;
11702                     this._jsPlumb.instance.removeElement(this._jsPlumb.div);
11703                 }
11704             }
11705             else {
11706                 // if not a forced cleanup, just detach child from parent for now.
11707                 if (this._jsPlumb && this._jsPlumb.div && this._jsPlumb.div.parentNode) {
11708                     this._jsPlumb.div.parentNode.removeChild(this._jsPlumb.div);
11709                 }
11710                 this.detached = true;
11711             }
11712 
11713         },
11714         reattach:function(instance) {
11715             if (this._jsPlumb.div != null) {
11716                 instance.getContainer().appendChild(this._jsPlumb.div);
11717             }
11718             this.detached = false;
11719         },
11720         computeMaxSize: function () {
11721             var td = _getDimensions(this);
11722             return Math.max(td[0], td[1]);
11723         },
11724         paint: function (p, containerExtents) {
11725             if (!this._jsPlumb.initialised) {
11726                 this.getElement();
11727                 p.component.appendDisplayElement(this._jsPlumb.div);
11728                 this._jsPlumb.initialised = true;
11729                 if (this.detached) {
11730                     this._jsPlumb.div.parentNode.removeChild(this._jsPlumb.div);
11731                 }
11732             }
11733             this._jsPlumb.div.style.left = (p.component.x + p.d.minx) + "px";
11734             this._jsPlumb.div.style.top = (p.component.y + p.d.miny) + "px";
11735         }
11736     });
11737 
11738     /*
11739      * Class: Overlays.Custom
11740      * A Custom overlay. You supply a 'create' function which returns some DOM element, and jsPlumb positions it.
11741      * The 'create' function is passed a Connection or Endpoint.
11742      */
11743     /*
11744      * Function: Constructor
11745      * 
11746      * Parameters:
11747      * create - function for jsPlumb to call that returns a DOM element.
11748      * location - distance (as a decimal from 0 to 1 inclusive) marking where the label should sit on the connector. defaults to 0.5.
11749      * id - optional id to use for later retrieval of this overlay.
11750      *
11751      */
11752     _jp.Overlays.Custom = function (params) {
11753         this.type = "Custom";
11754         AbstractDOMOverlay.apply(this, arguments);
11755     };
11756     _ju.extend(_jp.Overlays.Custom, AbstractDOMOverlay);
11757 
11758     _jp.Overlays.GuideLines = function () {
11759         var self = this;
11760         self.length = 50;
11761         self.strokeWidth = 5;
11762         this.type = "GuideLines";
11763         AbstractOverlay.apply(this, arguments);
11764         _jp.jsPlumbUIComponent.apply(this, arguments);
11765         this.draw = function (connector, currentConnectionPaintStyle) {
11766 
11767             var head = connector.pointAlongPathFrom(self.loc, self.length / 2),
11768                 mid = connector.pointOnPath(self.loc),
11769                 tail = _jg.pointOnLine(head, mid, self.length),
11770                 tailLine = _jg.perpendicularLineTo(head, tail, 40),
11771                 headLine = _jg.perpendicularLineTo(tail, head, 20);
11772 
11773             return {
11774                 connector: connector,
11775                 head: head,
11776                 tail: tail,
11777                 headLine: headLine,
11778                 tailLine: tailLine,
11779                 minX: Math.min(head.x, tail.x, headLine[0].x, headLine[1].x),
11780                 minY: Math.min(head.y, tail.y, headLine[0].y, headLine[1].y),
11781                 maxX: Math.max(head.x, tail.x, headLine[0].x, headLine[1].x),
11782                 maxY: Math.max(head.y, tail.y, headLine[0].y, headLine[1].y)
11783             };
11784         };
11785 
11786         // this.cleanup = function() { };  // nothing to clean up for GuideLines
11787     };
11788 
11789     /*
11790      * Class: Overlays.Label
11791 
11792      */
11793     /*
11794      * Function: Constructor
11795      * 
11796      * Parameters:
11797      * cssClass - optional css class string to append to css class. This string is appended "as-is", so you can of course have multiple classes
11798      *             defined.  This parameter is preferred to using labelStyle, borderWidth and borderStyle.
11799      * label - the label to paint.  May be a string or a function that returns a string.  Nothing will be painted if your label is null or your
11800      *         label function returns null.  empty strings _will_ be painted.
11801      * location - distance (as a decimal from 0 to 1 inclusive) marking where the label should sit on the connector. defaults to 0.5.
11802      * id - optional id to use for later retrieval of this overlay.
11803      * 
11804      *
11805      */
11806     _jp.Overlays.Label = function (params) {
11807         this.labelStyle = params.labelStyle;
11808 
11809         var labelWidth = null, labelHeight = null, labelText = null, labelPadding = null;
11810         this.cssClass = this.labelStyle != null ? this.labelStyle.cssClass : null;
11811         var p = _jp.extend({
11812             create: function () {
11813                 return _jp.createElement("div");
11814             }}, params);
11815         _jp.Overlays.Custom.call(this, p);
11816         this.type = "Label";
11817         this.label = params.label || "";
11818         this.labelText = null;
11819         if (this.labelStyle) {
11820             var el = this.getElement();
11821             this.labelStyle.font = this.labelStyle.font || "12px sans-serif";
11822             el.style.font = this.labelStyle.font;
11823             el.style.color = this.labelStyle.color || "black";
11824             if (this.labelStyle.fill) {
11825                 el.style.background = this.labelStyle.fill;
11826             }
11827             if (this.labelStyle.borderWidth > 0) {
11828                 var dStyle = this.labelStyle.borderStyle ? this.labelStyle.borderStyle : "black";
11829                 el.style.border = this.labelStyle.borderWidth + "px solid " + dStyle;
11830             }
11831             if (this.labelStyle.padding) {
11832                 el.style.padding = this.labelStyle.padding;
11833             }
11834         }
11835 
11836     };
11837     _ju.extend(_jp.Overlays.Label, _jp.Overlays.Custom, {
11838         cleanup: function (force) {
11839             if (force) {
11840                 this.div = null;
11841                 this.label = null;
11842                 this.labelText = null;
11843                 this.cssClass = null;
11844                 this.labelStyle = null;
11845             }
11846         },
11847         getLabel: function () {
11848             return this.label;
11849         },
11850         /*
11851          * Function: setLabel
11852          * sets the label's, um, label.  you would think i'd call this function
11853          * 'setText', but you can pass either a Function or a String to this, so
11854          * it makes more sense as 'setLabel'. This uses innerHTML on the label div, so keep
11855          * that in mind if you need escaped HTML.
11856          */
11857         setLabel: function (l) {
11858             this.label = l;
11859             this.labelText = null;
11860             this.clearCachedDimensions();
11861             this.update();
11862             this.component.repaint();
11863         },
11864         getDimensions: function () {
11865             this.update();
11866             return AbstractDOMOverlay.prototype.getDimensions.apply(this, arguments);
11867         },
11868         update: function () {
11869             if (typeof this.label === "function") {
11870                 var lt = this.label(this);
11871                 this.getElement().innerHTML = lt.replace(/\r\n/g, "<br/>");
11872             }
11873             else {
11874                 if (this.labelText == null) {
11875                     this.labelText = this.label;
11876                     this.getElement().innerHTML = this.labelText.replace(/\r\n/g, "<br/>");
11877                 }
11878             }
11879         },
11880         updateFrom:function(d) {
11881             if(d.label != null){
11882                 this.setLabel(d.label);
11883             }
11884         }
11885     });
11886 
11887     // ********************************* END OF OVERLAY DEFINITIONS ***********************************************************************
11888 
11889 }).call(typeof window !== 'undefined' ? window : this);
11890 
11891 /*
11892  * jsPlumb Community Edition
11893  *
11894  * Provides a way to visually connect elements on an HTML page, using SVG.
11895  *
11896  * This file contains the base class for library adapters.
11897  *
11898  * Copyright (c) 2010 - 2017 jsPlumb (hello@jsplumbtoolkit.com)
11899  *
11900  * https://jsplumbtoolkit.com
11901  * https://github.com/jsplumb/jsplumb
11902  *
11903  * Dual licensed under the MIT and GPL2 licenses.
11904  */
11905 ;(function() {
11906     "use strict";
11907     var root = this,
11908         _jp = root.jsPlumb;
11909 
11910     var _getEventManager = function(instance) {
11911         var e = instance._mottle;
11912         if (!e) {
11913             e = instance._mottle = new root.Mottle();
11914         }
11915         return e;
11916     };
11917 
11918     _jp.extend(root.jsPlumbInstance.prototype, {
11919         getEventManager:function() {
11920             return _getEventManager(this);
11921         },
11922         on : function(el, event, callback) {
11923             // TODO: here we would like to map the tap event if we know its
11924             // an internal bind to a click. we have to know its internal because only
11925             // then can we be sure that the UP event wont be consumed (tap is a synthesized
11926             // event from a mousedown followed by a mouseup).
11927             //event = { "click":"tap", "dblclick":"dbltap"}[event] || event;
11928             this.getEventManager().on.apply(this, arguments);
11929             return this;
11930         },
11931         off : function(el, event, callback) {
11932             this.getEventManager().off.apply(this, arguments);
11933             return this;
11934         }
11935     });
11936 
11937 
11938 }).call(typeof window !== 'undefined' ? window : this);
11939 /*
11940  * jsPlumb Community Edition
11941  *
11942  * Provides a way to visually connect elements on an HTML page, using SVG.
11943  *
11944  * This file contains the code for working with Groups.
11945  *
11946  * Copyright (c) 2010 - 2017 jsPlumb (hello@jsplumbtoolkit.com)
11947  *
11948  * https://jsplumbtoolkit.com
11949  * https://github.com/jsplumb/jsplumb
11950  *
11951  * Dual licensed under the MIT and GPL2 licenses.
11952  */
11953 ;(function() {
11954     "use strict";
11955 
11956     var root = this,
11957         _ju = root.jsPlumbUtil,
11958         _jpi = root.jsPlumbInstance;
11959 
11960     var GROUP_COLLAPSED_CLASS = "jtk-group-collapsed";
11961     var GROUP_EXPANDED_CLASS = "jtk-group-expanded";
11962     var GROUP_CONTAINER_SELECTOR = "[jtk-group-content]";
11963     var ELEMENT_DRAGGABLE_EVENT = "elementDraggable";
11964     var STOP = "stop";
11965     var REVERT = "revert";
11966     var GROUP_MANAGER = "_groupManager";
11967     var GROUP = "_jsPlumbGroup";
11968     var GROUP_DRAG_SCOPE = "_jsPlumbGroupDrag";
11969     var EVT_CHILD_ADDED = "group:addMember";
11970     var EVT_CHILD_REMOVED = "group:removeMember";
11971     var EVT_GROUP_ADDED = "group:add";
11972     var EVT_GROUP_REMOVED = "group:remove";
11973     var EVT_EXPAND = "group:expand";
11974     var EVT_COLLAPSE = "group:collapse";
11975     var EVT_GROUP_DRAG_STOP = "groupDragStop";
11976     var EVT_CONNECTION_MOVED = "connectionMoved";
11977     var EVT_INTERNAL_CONNECTION_DETACHED = "internal.connectionDetached";
11978 
11979     var CMD_REMOVE_ALL = "removeAll";
11980     var CMD_ORPHAN_ALL = "orphanAll";
11981     var CMD_SHOW = "show";
11982     var CMD_HIDE = "hide";
11983 
11984     var GroupManager = function(_jsPlumb) {
11985         var _managedGroups = {}, _connectionSourceMap = {}, _connectionTargetMap = {}, self = this;
11986 
11987         _jsPlumb.bind("connection", function(p) {
11988             if (p.source[GROUP] != null && p.target[GROUP] != null && p.source[GROUP] === p.target[GROUP]) {
11989                 _connectionSourceMap[p.connection.id] = p.source[GROUP];
11990                 _connectionTargetMap[p.connection.id] = p.source[GROUP];
11991             }
11992             else {
11993                 if (p.source[GROUP] != null) {
11994                     _ju.suggest(p.source[GROUP].connections.source, p.connection);
11995                     _connectionSourceMap[p.connection.id] = p.source[GROUP];
11996                 }
11997                 if (p.target[GROUP] != null) {
11998                     _ju.suggest(p.target[GROUP].connections.target, p.connection);
11999                     _connectionTargetMap[p.connection.id] = p.target[GROUP];
12000                 }
12001             }
12002         });
12003 
12004         function _cleanupDetachedConnection(conn) {
12005             delete conn.proxies;
12006             var group = _connectionSourceMap[conn.id], f;
12007             if (group != null) {
12008                 f = function(c) { return c.id === conn.id; };
12009                 _ju.removeWithFunction(group.connections.source, f);
12010                 _ju.removeWithFunction(group.connections.target, f);
12011                 delete _connectionSourceMap[conn.id];
12012             }
12013 
12014             group = _connectionTargetMap[conn.id];
12015             if (group != null) {
12016                 f = function(c) { return c.id === conn.id; };
12017                 _ju.removeWithFunction(group.connections.source, f);
12018                 _ju.removeWithFunction(group.connections.target, f);
12019                 delete _connectionTargetMap[conn.id];
12020             }
12021         }
12022 
12023         _jsPlumb.bind(EVT_INTERNAL_CONNECTION_DETACHED, function(p) {
12024             _cleanupDetachedConnection(p.connection);
12025         });
12026 
12027         _jsPlumb.bind(EVT_CONNECTION_MOVED, function(p) {
12028             var connMap = p.index === 0 ? _connectionSourceMap : _connectionTargetMap;
12029             var group = connMap[p.connection.id];
12030             if (group) {
12031                 var list = group.connections[p.index === 0 ? "source" : "target"];
12032                 var idx = list.indexOf(p.connection);
12033                 if (idx !== -1) {
12034                     list.splice(idx, 1);
12035                 }
12036             }
12037         });
12038 
12039         this.addGroup = function(group) {
12040             _jsPlumb.addClass(group.getEl(), GROUP_EXPANDED_CLASS);
12041             _managedGroups[group.id] = group;
12042             group.manager = this;
12043             _updateConnectionsForGroup(group);
12044             _jsPlumb.fire(EVT_GROUP_ADDED, { group:group });
12045         };
12046 
12047         this.addToGroup = function(group, el, doNotFireEvent) {
12048             group = this.getGroup(group);
12049             if (group) {
12050                 //group.add(el, doNotFireEvent);
12051                 var groupEl = group.getEl();
12052 
12053                 if (el._isJsPlumbGroup) {
12054                     return;
12055                 }
12056                 var currentGroup = el._jsPlumbGroup;
12057                 // if already a member of this group, do nothing
12058                 if (currentGroup !== group) {
12059                     var elpos = _jsPlumb.getOffset(el, true);
12060                     var cpos = group.collapsed ? _jsPlumb.getOffset(groupEl, true) : _jsPlumb.getOffset(group.getDragArea(), true);
12061 
12062                     // otherwise, transfer to this group.
12063                     if (currentGroup != null) {
12064                         currentGroup.remove(el, doNotFireEvent);
12065                         self.updateConnectionsForGroup(currentGroup);
12066                     }
12067                     group.add(el, doNotFireEvent);
12068 
12069                     var handleDroppedConnections = function (list, index) {
12070                         var oidx = index === 0 ? 1 : 0;
12071                         list.each(function (c) {
12072                             c.setVisible(false);
12073                             if (c.endpoints[oidx].element._jsPlumbGroup === group) {
12074                                 c.endpoints[oidx].setVisible(false);
12075                                 self.expandConnection(c, oidx, group);
12076                             }
12077                             else {
12078                                 c.endpoints[index].setVisible(false);
12079                                 self.collapseConnection(c, index, group);
12080                             }
12081                         });
12082                     };
12083 
12084                     if (group.collapsed) {
12085                         handleDroppedConnections(_jsPlumb.select({source: el}), 0);
12086                         handleDroppedConnections(_jsPlumb.select({target: el}), 1);
12087                     }
12088 
12089                     var elId = _jsPlumb.getId(el);
12090                     _jsPlumb.dragManager.setParent(el, elId, groupEl, _jsPlumb.getId(groupEl), elpos);
12091 
12092                     var newPosition = { left: elpos.left - cpos.left, top: elpos.top - cpos.top };
12093 
12094                     _jsPlumb.setPosition(el, newPosition);
12095 
12096                     _jsPlumb.dragManager.revalidateParent(el, elId, elpos);
12097 
12098                     self.updateConnectionsForGroup(group);
12099 
12100                     _jsPlumb.revalidate(elId);
12101 
12102                     setTimeout(function () {
12103                         _jsPlumb.fire(EVT_CHILD_ADDED, {group: group, el: el});
12104                     }, 0);
12105                 }
12106             }
12107         };
12108 
12109         this.removeFromGroup = function(group, el, doNotFireEvent) {
12110             group = this.getGroup(group);
12111             if (group) {
12112                 group.remove(el, null, doNotFireEvent);
12113             }
12114         };
12115 
12116         this.getGroup = function(groupId) {
12117             var group = groupId;
12118             if (_ju.isString(groupId)) {
12119                 group = _managedGroups[groupId];
12120                 if (group == null) {
12121                     throw new TypeError("No such group [" + groupId + "]");
12122                 }
12123             }
12124             return group;
12125         };
12126 
12127         this.getGroups = function() {
12128             var o = [];
12129             for (var g in _managedGroups) {
12130                 o.push(_managedGroups[g]);
12131             }
12132             return o;
12133         };
12134 
12135         this.removeGroup = function(group, deleteMembers, manipulateDOM, doNotFireEvent) {
12136             group = this.getGroup(group);
12137             this.expandGroup(group, true); // this reinstates any original connections and removes all proxies, but does not fire an event.
12138             group[deleteMembers ? CMD_REMOVE_ALL : CMD_ORPHAN_ALL](manipulateDOM, doNotFireEvent);
12139             _jsPlumb.remove(group.getEl());
12140             delete _managedGroups[group.id];
12141             delete _jsPlumb._groups[group.id];
12142             _jsPlumb.fire(EVT_GROUP_REMOVED, { group:group });
12143         };
12144 
12145         this.removeAllGroups = function(deleteMembers, manipulateDOM, doNotFireEvent) {
12146             for (var g in _managedGroups) {
12147                 this.removeGroup(_managedGroups[g], deleteMembers, manipulateDOM, doNotFireEvent);
12148             }
12149         };
12150 
12151         function _setVisible(group, state) {
12152             var m = group.getMembers();
12153             for (var i = 0; i < m.length; i++) {
12154                 _jsPlumb[state ? CMD_SHOW : CMD_HIDE](m[i], true);
12155             }
12156         }
12157 
12158         var _collapseConnection = this.collapseConnection = function(c, index, group) {
12159 
12160             var proxyEp, groupEl = group.getEl(), groupElId = _jsPlumb.getId(groupEl),
12161                 originalElementId = c.endpoints[index].elementId;
12162 
12163             var otherEl = c.endpoints[index === 0 ? 1 : 0].element;
12164             if (otherEl[GROUP] && (!otherEl[GROUP].shouldProxy() && otherEl[GROUP].collapsed)) {
12165                 return;
12166             }
12167 
12168             c.proxies = c.proxies || [];
12169             if(c.proxies[index]) {
12170                 proxyEp = c.proxies[index].ep;
12171             }else {
12172                 proxyEp = _jsPlumb.addEndpoint(groupEl, {
12173                     endpoint:group.getEndpoint(c, index),
12174                     anchor:group.getAnchor(c, index),
12175                     parameters:{
12176                         isProxyEndpoint:true
12177                     }
12178                 });
12179             }
12180             proxyEp.setDeleteOnEmpty(true);
12181 
12182             // for this index, stash proxy info: the new EP, the original EP.
12183             c.proxies[index] = { ep:proxyEp, originalEp: c.endpoints[index] };
12184 
12185             // and advise the anchor manager
12186             if (index === 0) {
12187                 // TODO why are there two differently named methods? Why is there not one method that says "some end of this
12188                 // connection changed (you give the index), and here's the new element and element id."
12189                 _jsPlumb.anchorManager.sourceChanged(originalElementId, groupElId, c, groupEl);
12190             }
12191             else {
12192                 _jsPlumb.anchorManager.updateOtherEndpoint(c.endpoints[0].elementId, originalElementId, groupElId, c);
12193                 c.target = groupEl;
12194                 c.targetId = groupElId;
12195             }
12196 
12197 
12198             // detach the original EP from the connection.
12199             c.proxies[index].originalEp.detachFromConnection(c, null, true);
12200 
12201             // set the proxy as the new ep
12202             proxyEp.connections = [ c ];
12203             c.endpoints[index] = proxyEp;
12204 
12205             c.setVisible(true);
12206         };
12207 
12208         this.collapseGroup = function(group) {
12209             group = this.getGroup(group);
12210             if (group == null || group.collapsed) {
12211                 return;
12212             }
12213             var groupEl = group.getEl();
12214 
12215             // todo remove old proxy endpoints first, just in case?
12216             //group.proxies.length = 0;
12217 
12218             // hide all connections
12219             _setVisible(group, false);
12220 
12221             if (group.shouldProxy()) {
12222                 // collapses all connections in a group.
12223                 var _collapseSet = function (conns, index) {
12224                     for (var i = 0; i < conns.length; i++) {
12225                         var c = conns[i];
12226                         _collapseConnection(c, index, group);
12227                     }
12228                 };
12229 
12230                 // setup proxies for sources and targets
12231                 _collapseSet(group.connections.source, 0);
12232                 _collapseSet(group.connections.target, 1);
12233             }
12234 
12235             group.collapsed = true;
12236             _jsPlumb.removeClass(groupEl, GROUP_EXPANDED_CLASS);
12237             _jsPlumb.addClass(groupEl, GROUP_COLLAPSED_CLASS);
12238             _jsPlumb.revalidate(groupEl);
12239             _jsPlumb.fire(EVT_COLLAPSE, { group:group  });
12240         };
12241 
12242         var _expandConnection = this.expandConnection = function(c, index, group) {
12243 
12244             // if no proxies or none for this end of the connection, abort.
12245             if (c.proxies == null || c.proxies[index] == null) {
12246                 return;
12247             }
12248 
12249             var groupElId = _jsPlumb.getId(group.getEl()),
12250                 originalElement = c.proxies[index].originalEp.element,
12251                 originalElementId = c.proxies[index].originalEp.elementId;
12252 
12253             c.endpoints[index] = c.proxies[index].originalEp;
12254             // and advise the anchor manager
12255             if (index === 0) {
12256                 // TODO why are there two differently named methods? Why is there not one method that says "some end of this
12257                 // connection changed (you give the index), and here's the new element and element id."
12258                 _jsPlumb.anchorManager.sourceChanged(groupElId, originalElementId, c, originalElement);
12259             }
12260             else {
12261                 _jsPlumb.anchorManager.updateOtherEndpoint(c.endpoints[0].elementId, groupElId, originalElementId, c);
12262                 c.target = originalElement;
12263                 c.targetId = originalElementId;
12264             }
12265 
12266             // detach the proxy EP from the connection (which will cause it to be removed as we no longer need it)
12267             c.proxies[index].ep.detachFromConnection(c, null);
12268 
12269             c.proxies[index].originalEp.addConnection(c);
12270 
12271             // cleanup
12272             delete c.proxies[index];
12273         };
12274 
12275         this.expandGroup = function(group, doNotFireEvent) {
12276 
12277             group = this.getGroup(group);
12278 
12279             if (group == null || !group.collapsed) {
12280                 return;
12281             }
12282             var groupEl = group.getEl();
12283 
12284             _setVisible(group, true);
12285 
12286             if (group.shouldProxy()) {
12287                 // collapses all connections in a group.
12288                 var _expandSet = function (conns, index) {
12289                     for (var i = 0; i < conns.length; i++) {
12290                         var c = conns[i];
12291                         _expandConnection(c, index, group);
12292                     }
12293                 };
12294 
12295                 // setup proxies for sources and targets
12296                 _expandSet(group.connections.source, 0);
12297                 _expandSet(group.connections.target, 1);
12298             }
12299 
12300             group.collapsed = false;
12301             _jsPlumb.addClass(groupEl, GROUP_EXPANDED_CLASS);
12302             _jsPlumb.removeClass(groupEl, GROUP_COLLAPSED_CLASS);
12303             _jsPlumb.revalidate(groupEl);
12304             this.repaintGroup(group);
12305             if (!doNotFireEvent) {
12306                 _jsPlumb.fire(EVT_EXPAND, { group: group});
12307             }
12308         };
12309 
12310         this.repaintGroup = function(group) {
12311             group = this.getGroup(group);
12312             var m = group.getMembers();
12313             for (var i = 0; i < m.length; i++) {
12314                 _jsPlumb.revalidate(m[i]);
12315             }
12316         };
12317 
12318         // TODO refactor this with the code that responds to `connection` events.
12319         function _updateConnectionsForGroup(group) {
12320             var members = group.getMembers();
12321             var c1 = _jsPlumb.getConnections({source:members}, true);
12322             var c2 = _jsPlumb.getConnections({target:members}, true);
12323             var processed = {};
12324             group.connections.source.length = 0;
12325             group.connections.target.length = 0;
12326             var oneSet = function(c) {
12327                 for (var i = 0; i < c.length; i++) {
12328                     if (processed[c[i].id]) {
12329                         continue;
12330                     }
12331                     processed[c[i].id] = true;
12332                     if (c[i].source._jsPlumbGroup === group) {
12333                         if (c[i].target._jsPlumbGroup !== group) {
12334                             group.connections.source.push(c[i]);
12335                         }
12336                         _connectionSourceMap[c[i].id] = group;
12337                     }
12338                     else if (c[i].target._jsPlumbGroup === group) {
12339                         group.connections.target.push(c[i]);
12340                         _connectionTargetMap[c[i].id] = group;
12341                     }
12342                 }
12343             };
12344             oneSet(c1); oneSet(c2);
12345         }
12346 
12347         this.updateConnectionsForGroup = _updateConnectionsForGroup;
12348         this.refreshAllGroups = function() {
12349             for (var g in _managedGroups) {
12350                 _updateConnectionsForGroup(_managedGroups[g]);
12351                 _jsPlumb.dragManager.updateOffsets(_jsPlumb.getId(_managedGroups[g].getEl()));
12352             }
12353         };
12354     };
12355 
12356     /**
12357      *
12358      * @param {jsPlumbInstance} _jsPlumb Associated jsPlumb instance.
12359      * @param {Object} params
12360      * @param {Element} params.el The DOM element representing the Group.
12361      * @param {String} [params.id] Optional ID for the Group. A UUID will be assigned as the Group's ID if you do not provide one.
12362      * @param {Boolean} [params.constrain=false] If true, child elements will not be able to be dragged outside of the Group container.
12363      * @param {Boolean} [params.revert=true] By default, child elements revert to the container if dragged outside. You can change this by setting `revert:false`. This behaviour is also overridden if you set `orphan` or `prune`.
12364      * @param {Boolean} [params.orphan=false] If true, child elements dropped outside of the Group container will be removed from the Group (but not from the DOM).
12365      * @param {Boolean} [params.prune=false] If true, child elements dropped outside of the Group container will be removed from the Group and also from the DOM.
12366      * @param {Boolean} [params.dropOverride=false] If true, a child element that has been dropped onto some other Group will not be subject to the controls imposed by `prune`, `revert` or `orphan`.
12367      * @constructor
12368      */
12369     var Group = function(_jsPlumb, params) {
12370         var self = this;
12371         var el = params.el;
12372         this.getEl = function() { return el; };
12373         this.id = params.id || _ju.uuid();
12374         el._isJsPlumbGroup = true;
12375 
12376         var getDragArea = this.getDragArea = function() {
12377             var da = _jsPlumb.getSelector(el, GROUP_CONTAINER_SELECTOR);
12378             return da && da.length > 0 ? da[0] : el;
12379         };
12380 
12381         var ghost = params.ghost === true;
12382         var constrain = ghost || (params.constrain === true);
12383         var revert = params.revert !== false;
12384         var orphan = params.orphan === true;
12385         var prune = params.prune === true;
12386         var dropOverride = params.dropOverride === true;
12387         var proxied = params.proxied !== false;
12388         var elements = [];
12389         this.connections = { source:[], target:[], internal:[] };
12390 
12391         // this function, and getEndpoint below, are stubs for a future setup in which we can choose endpoint
12392         // and anchor based upon the connection and the index (source/target) of the endpoint to be proxied.
12393         this.getAnchor = function(conn, endpointIndex) {
12394             return params.anchor || "Continuous";
12395         };
12396 
12397         this.getEndpoint = function(conn, endpointIndex) {
12398             return params.endpoint || [ "Dot", { radius:10 }];
12399         };
12400 
12401         this.collapsed = false;
12402         if (params.draggable !== false) {
12403             var opts = {
12404                 stop:function(params) {
12405                     _jsPlumb.fire(EVT_GROUP_DRAG_STOP, jsPlumb.extend(params, {group:self}));
12406                 },
12407                 scope:GROUP_DRAG_SCOPE
12408             };
12409             if (params.dragOptions) {
12410                 root.jsPlumb.extend(opts, params.dragOptions);
12411             }
12412             _jsPlumb.draggable(params.el, opts);
12413         }
12414         if (params.droppable !== false) {
12415             _jsPlumb.droppable(params.el, {
12416                 drop:function(p) {
12417                     var el = p.drag.el;
12418                     if (el._isJsPlumbGroup) {
12419                         return;
12420                     }
12421                     var currentGroup = el._jsPlumbGroup;
12422                     if (currentGroup !== self) {
12423                         if (currentGroup != null) {
12424                             if (currentGroup.overrideDrop(el, self)) {
12425                                 return;
12426                             }
12427                         }
12428                         _jsPlumb.getGroupManager().addToGroup(self, el, false);
12429                     }
12430 
12431                 }
12432             });
12433         }
12434         var _each = function(_el, fn) {
12435             var els = _el.nodeType == null ?  _el : [ _el ];
12436             for (var i = 0; i < els.length; i++) {
12437                 fn(els[i]);
12438             }
12439         };
12440 
12441         this.overrideDrop = function(_el, targetGroup) {
12442             return dropOverride && (revert || prune || orphan);
12443         };
12444 
12445         this.add = function(_el, doNotFireEvent) {
12446             var dragArea = getDragArea();
12447             _each(_el, function(__el) {
12448 
12449                 if (__el._jsPlumbGroup != null) {
12450                     if (__el._jsPlumbGroup === self) {
12451                         return;
12452                     } else {
12453                         __el._jsPlumbGroup.remove(__el, true, doNotFireEvent, false);
12454                     }
12455                 }
12456 
12457                 __el._jsPlumbGroup = self;
12458                 elements.push(__el);
12459                 // test if draggable and add handlers if so.
12460                 if (_jsPlumb.isAlreadyDraggable(__el)) {
12461                     _bindDragHandlers(__el);
12462                 }
12463 
12464                 if (__el.parentNode !== dragArea) {
12465                     dragArea.appendChild(__el);
12466                 }
12467 
12468                 if (!doNotFireEvent) {
12469                     _jsPlumb.fire(EVT_CHILD_ADDED, {group: self, el: __el});
12470                 }
12471             });
12472 
12473             _jsPlumb.getGroupManager().updateConnectionsForGroup(self);
12474         };
12475 
12476         this.remove = function(el, manipulateDOM, doNotFireEvent, doNotUpdateConnections) {
12477 
12478             _each(el, function(__el) {
12479                 delete __el._jsPlumbGroup;
12480                 _ju.removeWithFunction(elements, function(e) {
12481                     return e === __el;
12482                 });
12483 
12484                 if (manipulateDOM) {
12485                     try { self.getDragArea().removeChild(__el); }
12486                     catch (e) {
12487                         jsPlumbUtil.log("Could not remove element from Group " + e);
12488                     }
12489                 }
12490                 _unbindDragHandlers(__el);
12491                 if (!doNotFireEvent) {
12492                     _jsPlumb.fire(EVT_CHILD_REMOVED, {group: self, el: __el});
12493                 }
12494             });
12495             if (!doNotUpdateConnections) {
12496                 _jsPlumb.getGroupManager().updateConnectionsForGroup(self);
12497             }
12498         };
12499         this.removeAll = function(manipulateDOM, doNotFireEvent) {
12500             for (var i = 0, l = elements.length; i < l; i++) {
12501                 self.remove(elements[0], manipulateDOM, doNotFireEvent, true);
12502             }
12503             elements.length = 0;
12504             _jsPlumb.getGroupManager().updateConnectionsForGroup(self);
12505         };
12506         this.orphanAll = function() {
12507             for (var i = 0; i < elements.length; i++) {
12508                 _orphan(elements[i]);
12509             }
12510             elements.length = 0;
12511         };
12512         this.getMembers = function() { return elements; };
12513 
12514         el[GROUP] = this;
12515 
12516         _jsPlumb.bind(ELEMENT_DRAGGABLE_EVENT, function(dragParams) {
12517             // if its for the current group,
12518             if (dragParams.el._jsPlumbGroup === this) {
12519                 _bindDragHandlers(dragParams.el);
12520             }
12521         }.bind(this));
12522 
12523         function _findParent(_el) {
12524             return _el.offsetParent;
12525         }
12526 
12527         function _isInsideParent(_el, pos) {
12528             var p = _findParent(_el),
12529                 s = _jsPlumb.getSize(p),
12530                 ss = _jsPlumb.getSize(_el),
12531                 leftEdge = pos[0],
12532                 rightEdge = leftEdge + ss[0],
12533                 topEdge = pos[1],
12534                 bottomEdge = topEdge + ss[1];
12535 
12536             return rightEdge > 0 && leftEdge < s[0] && bottomEdge > 0 && topEdge < s[1];
12537         }
12538 
12539         //
12540         // orphaning an element means taking it out of the group and adding it to the main jsplumb container.
12541         //
12542         function _orphan(_el) {
12543             var id = _jsPlumb.getId(_el);
12544             var pos = _jsPlumb.getOffset(_el);
12545             _el.parentNode.removeChild(_el);
12546             _jsPlumb.getContainer().appendChild(_el);
12547             _jsPlumb.setPosition(_el, pos);
12548             delete _el._jsPlumbGroup;
12549             _unbindDragHandlers(_el);
12550             _jsPlumb.dragManager.clearParent(_el, id);
12551         }
12552 
12553         //
12554         // remove an element from the group, then either prune it from the jsplumb instance, or just orphan it.
12555         //
12556         function _pruneOrOrphan(p) {
12557             if (!_isInsideParent(p.el, p.pos)) {
12558                 var group = p.el._jsPlumbGroup;
12559                 if (prune) {
12560                     _jsPlumb.remove(p.el);
12561                 } else {
12562                     _orphan(p.el);
12563                 }
12564 
12565                 group.remove(p.el);
12566             }
12567         }
12568 
12569         //
12570         // redraws the element
12571         //
12572         function _revalidate(_el) {
12573             var id = _jsPlumb.getId(_el);
12574             _jsPlumb.revalidate(_el);
12575             _jsPlumb.dragManager.revalidateParent(_el, id);
12576         }
12577 
12578         //
12579         // unbind the group specific drag/revert handlers.
12580         //
12581         function _unbindDragHandlers(_el) {
12582             if (!_el._katavorioDrag) {
12583                 return;
12584             }
12585             if (prune || orphan) {
12586                 _el._katavorioDrag.off(STOP, _pruneOrOrphan);
12587             }
12588             if (!prune && !orphan && revert) {
12589                 _el._katavorioDrag.off(REVERT, _revalidate);
12590                 _el._katavorioDrag.setRevert(null);
12591             }
12592         }
12593 
12594         function _bindDragHandlers(_el) {
12595             if (!_el._katavorioDrag) {
12596                 return;
12597             }
12598             if (prune || orphan) {
12599                 _el._katavorioDrag.on(STOP, _pruneOrOrphan);
12600             }
12601 
12602             if (constrain) {
12603                 _el._katavorioDrag.setConstrain(true);
12604             }
12605 
12606             if (ghost) {
12607                 _el._katavorioDrag.setUseGhostProxy(true);
12608             }
12609 
12610             if (!prune && !orphan && revert) {
12611                 _el._katavorioDrag.on(REVERT, _revalidate);
12612                 _el._katavorioDrag.setRevert(function(__el, pos) {
12613                     return !_isInsideParent(__el, pos);
12614                 });
12615             }
12616         }
12617 
12618         this.shouldProxy = function() {
12619             return proxied;
12620         };
12621 
12622         _jsPlumb.getGroupManager().addGroup(this);
12623     };
12624 
12625     /**
12626      * Adds a group to the jsPlumb instance.
12627      * @method addGroup
12628      * @param {Object} params
12629      * @return {Group} The newly created Group.
12630      */
12631     _jpi.prototype.addGroup = function(params) {
12632         var j = this;
12633         j._groups = j._groups || {};
12634         if (j._groups[params.id] != null) {
12635             throw new TypeError("cannot create Group [" + params.id + "]; a Group with that ID exists");
12636         }
12637         if (params.el[GROUP] != null) {
12638             throw new TypeError("cannot create Group [" + params.id + "]; the given element is already a Group");
12639         }
12640         var group = new Group(j, params);
12641         j._groups[group.id] = group;
12642         if (params.collapsed) {
12643             this.collapseGroup(group);
12644         }
12645         return group;
12646     };
12647 
12648     /**
12649      * Add an element to a group.
12650      * @method addToGroup
12651      * @param {String} group Group, or ID of the group, to add the element to.
12652      * @param {Element} el Element to add to the group.
12653      */
12654     _jpi.prototype.addToGroup = function(group, el, doNotFireEvent) {
12655 
12656         var _one = function(_el) {
12657             var id = this.getId(_el);
12658             this.manage(id, _el);
12659             this.getGroupManager().addToGroup(group, _el, doNotFireEvent);
12660         }.bind(this);
12661 
12662         if (Array.isArray(el)) {
12663             for (var i = 0; i < el.length; i++) {
12664                 _one(el[i]);
12665             }
12666         } else {
12667             _one(el);
12668         }
12669     };
12670 
12671     /**
12672      * Remove an element from a group.
12673      * @method removeFromGroup
12674      * @param {String} group Group, or ID of the group, to remove the element from.
12675      * @param {Element} el Element to add to the group.
12676      */
12677     _jpi.prototype.removeFromGroup = function(group, el, doNotFireEvent) {
12678         this.getGroupManager().removeFromGroup(group, el, doNotFireEvent);
12679     };
12680 
12681     /**
12682      * Remove a group, and optionally remove its members from the jsPlumb instance.
12683      * @method removeGroup
12684      * @param {String|Group} group Group to delete, or ID of Group to delete.
12685      * @param {Boolean} [deleteMembers=false] If true, group members will be removed along with the group. Otherwise they will
12686      * just be 'orphaned' (returned to the main container).
12687      */
12688     _jpi.prototype.removeGroup = function(group, deleteMembers, manipulateDOM, doNotFireEvent) {
12689         this.getGroupManager().removeGroup(group, deleteMembers, manipulateDOM, doNotFireEvent);
12690     };
12691 
12692     /**
12693      * Remove all groups, and optionally remove their members from the jsPlumb instance.
12694      * @method removeAllGroup
12695      * @param {Boolean} [deleteMembers=false] If true, group members will be removed along with the groups. Otherwise they will
12696      * just be 'orphaned' (returned to the main container).
12697      */
12698     _jpi.prototype.removeAllGroups = function(deleteMembers, manipulateDOM, doNotFireEvent) {
12699         this.getGroupManager().removeAllGroups(deleteMembers, manipulateDOM, doNotFireEvent);
12700     };
12701 
12702     /**
12703      * Get a Group
12704      * @method getGroup
12705      * @param {String} groupId ID of the group to get
12706      * @return {Group} Group with the given ID, null if not found.
12707      */
12708     _jpi.prototype.getGroup = function(groupId) {
12709         return this.getGroupManager().getGroup(groupId);
12710     };
12711 
12712     /**
12713      * Gets all the Groups managed by the jsPlumb instance.
12714      * @returns {Group[]} List of Groups. Empty if none.
12715      */
12716     _jpi.prototype.getGroups = function() {
12717         return this.getGroupManager().getGroups();
12718     };
12719 
12720     /**
12721      * Expands a group element. jsPlumb doesn't do "everything" for you here, because what it means to expand a Group
12722      * will vary from application to application. jsPlumb does these things:
12723      *
12724      * - Hides any connections that are internal to the group (connections between members, and connections from member of
12725      * the group to the group itself)
12726      * - Proxies all connections for which the source or target is a member of the group.
12727      * - Hides the proxied connections.
12728      * - Adds the jtk-group-expanded class to the group's element
12729      * - Removes the jtk-group-collapsed class from the group's element.
12730      *
12731      * @method expandGroup
12732      * @param {String|Group} group Group to expand, or ID of Group to expand.
12733      */
12734     _jpi.prototype.expandGroup = function(group) {
12735         this.getGroupManager().expandGroup(group);
12736     };
12737 
12738     /**
12739      * Collapses a group element. jsPlumb doesn't do "everything" for you here, because what it means to collapse a Group
12740      * will vary from application to application. jsPlumb does these things:
12741      *
12742      * - Shows any connections that are internal to the group (connections between members, and connections from member of
12743      * the group to the group itself)
12744      * - Removes proxies for all connections for which the source or target is a member of the group.
12745      * - Shows the previously proxied connections.
12746      * - Adds the jtk-group-collapsed class to the group's element
12747      * - Removes the jtk-group-expanded class from the group's element.
12748      *
12749      * @method expandGroup
12750      * @param {String|Group} group Group to expand, or ID of Group to expand.
12751      */
12752     _jpi.prototype.collapseGroup = function(groupId) {
12753         this.getGroupManager().collapseGroup(groupId);
12754     };
12755 
12756 
12757     _jpi.prototype.repaintGroup = function(group) {
12758         this.getGroupManager().repaintGroup(group);
12759     };
12760 
12761     /**
12762      * Collapses or expands a group element depending on its current state. See notes in the collapseGroup and expandGroup method.
12763      *
12764      * @method toggleGroup
12765      * @param {String|Group} group Group to expand/collapse, or ID of Group to expand/collapse.
12766      */
12767     _jpi.prototype.toggleGroup = function(group) {
12768         group = this.getGroupManager().getGroup(group);
12769         if (group != null) {
12770             this.getGroupManager()[group.collapsed ? "expandGroup" : "collapseGroup"](group);
12771         }
12772     };
12773 
12774     //
12775     // lazy init a group manager for the given jsplumb instance.
12776     //
12777     _jpi.prototype.getGroupManager = function() {
12778         var mgr = this[GROUP_MANAGER];
12779         if (mgr == null) {
12780             mgr = this[GROUP_MANAGER] = new GroupManager(this);
12781         }
12782         return mgr;
12783     };
12784 
12785     _jpi.prototype.removeGroupManager = function() {
12786         delete this[GROUP_MANAGER];
12787     };
12788 
12789     /**
12790      * Gets the Group that the given element belongs to, null if none.
12791      * @method getGroupFor
12792      * @param {String|Element} el Element, or element ID.
12793      * @returns {Group} A Group, if found, or null.
12794      */
12795     _jpi.prototype.getGroupFor = function(el) {
12796         el = this.getElement(el);
12797         if (el) {
12798             return el[GROUP];
12799         }
12800     };
12801 
12802 }).call(typeof window !== 'undefined' ? window : this);
12803 
12804 
12805 /*
12806  * jsPlumb Community Edition
12807  * 
12808  * Provides a way to visually connect elements on an HTML page, using SVG.
12809  * 
12810  * This file contains the 'flowchart' connectors, consisting of vertical and horizontal line segments.
12811  *
12812  * Copyright (c) 2010 - 2017 jsPlumb (hello@jsplumbtoolkit.com)
12813  * 
12814  * https://jsplumbtoolkit.com
12815  * https://github.com/jsplumb/jsplumb
12816  * 
12817  * Dual licensed under the MIT and GPL2 licenses.
12818  */
12819 ;
12820 (function () {
12821 
12822     "use strict";
12823     var root = this, _jp = root.jsPlumb, _ju = root.jsPlumbUtil;
12824 
12825     var Flowchart = function (params) {
12826         this.type = "Flowchart";
12827         params = params || {};
12828         params.stub = params.stub == null ? 30 : params.stub;
12829         var segments,
12830             _super = _jp.Connectors.AbstractConnector.apply(this, arguments),
12831             midpoint = params.midpoint == null ? 0.5 : params.midpoint,
12832             alwaysRespectStubs = params.alwaysRespectStubs === true,
12833             lastx = null, lasty = null, lastOrientation,
12834             cornerRadius = params.cornerRadius != null ? params.cornerRadius : 0,
12835 
12836             // TODO now common between this and AbstractBezierEditor; refactor into superclass?
12837             loopbackRadius = params.loopbackRadius || 25,
12838             isLoopbackCurrently = false,
12839 
12840             sgn = function (n) {
12841                 return n < 0 ? -1 : n === 0 ? 0 : 1;
12842             },
12843             /**
12844              * helper method to add a segment.
12845              */
12846             addSegment = function (segments, x, y, paintInfo) {
12847                 if (lastx === x && lasty === y) {
12848                     return;
12849                 }
12850                 var lx = lastx == null ? paintInfo.sx : lastx,
12851                     ly = lasty == null ? paintInfo.sy : lasty,
12852                     o = lx === x ? "v" : "h",
12853                     sgnx = sgn(x - lx),
12854                     sgny = sgn(y - ly);
12855 
12856                 lastx = x;
12857                 lasty = y;
12858                 segments.push([lx, ly, x, y, o, sgnx, sgny]);
12859             },
12860             segLength = function (s) {
12861                 return Math.sqrt(Math.pow(s[0] - s[2], 2) + Math.pow(s[1] - s[3], 2));
12862             },
12863             _cloneArray = function (a) {
12864                 var _a = [];
12865                 _a.push.apply(_a, a);
12866                 return _a;
12867             },
12868             writeSegments = function (conn, segments, paintInfo) {
12869                 var current = null, next;
12870                 for (var i = 0; i < segments.length - 1; i++) {
12871 
12872                     current = current || _cloneArray(segments[i]);
12873                     next = _cloneArray(segments[i + 1]);
12874                     if (cornerRadius > 0 && current[4] !== next[4]) {
12875                         var radiusToUse = Math.min(cornerRadius, segLength(current), segLength(next));
12876                         // right angle. adjust current segment's end point, and next segment's start point.
12877                         current[2] -= current[5] * radiusToUse;
12878                         current[3] -= current[6] * radiusToUse;
12879                         next[0] += next[5] * radiusToUse;
12880                         next[1] += next[6] * radiusToUse;
12881                         var ac = (current[6] === next[5] && next[5] === 1) ||
12882                                 ((current[6] === next[5] && next[5] === 0) && current[5] !== next[6]) ||
12883                                 (current[6] === next[5] && next[5] === -1),
12884                             sgny = next[1] > current[3] ? 1 : -1,
12885                             sgnx = next[0] > current[2] ? 1 : -1,
12886                             sgnEqual = sgny === sgnx,
12887                             cx = (sgnEqual && ac || (!sgnEqual && !ac)) ? next[0] : current[2],
12888                             cy = (sgnEqual && ac || (!sgnEqual && !ac)) ? current[3] : next[1];
12889 
12890                         _super.addSegment(conn, "Straight", {
12891                             x1: current[0], y1: current[1], x2: current[2], y2: current[3]
12892                         });
12893 
12894                         _super.addSegment(conn, "Arc", {
12895                             r: radiusToUse,
12896                             x1: current[2],
12897                             y1: current[3],
12898                             x2: next[0],
12899                             y2: next[1],
12900                             cx: cx,
12901                             cy: cy,
12902                             ac: ac
12903                         });
12904                     }
12905                     else {
12906                         // dx + dy are used to adjust for line width.
12907                         var dx = (current[2] === current[0]) ? 0 : (current[2] > current[0]) ? (paintInfo.lw / 2) : -(paintInfo.lw / 2),
12908                             dy = (current[3] === current[1]) ? 0 : (current[3] > current[1]) ? (paintInfo.lw / 2) : -(paintInfo.lw / 2);
12909                         _super.addSegment(conn, "Straight", {
12910                             x1: current[0] - dx, y1: current[1] - dy, x2: current[2] + dx, y2: current[3] + dy
12911                         });
12912                     }
12913                     current = next;
12914                 }
12915                 if (next != null) {
12916                     // last segment
12917                     _super.addSegment(conn, "Straight", {
12918                         x1: next[0], y1: next[1], x2: next[2], y2: next[3]
12919                     });
12920                 }
12921             };
12922 
12923         this._compute = function (paintInfo, params) {
12924 
12925             segments = [];
12926             lastx = null;
12927             lasty = null;
12928             lastOrientation = null;
12929 
12930             var commonStubCalculator = function () {
12931                 return [ paintInfo.startStubX, paintInfo.startStubY, paintInfo.endStubX, paintInfo.endStubY ];
12932             },
12933                 stubCalculators = {
12934                     perpendicular: commonStubCalculator,
12935                     orthogonal: commonStubCalculator,
12936                     opposite: function (axis) {
12937                         var pi = paintInfo,
12938                             idx = axis === "x" ? 0 : 1,
12939                             areInProximity = {
12940                                 "x": function () {
12941                                     return ( (pi.so[idx] === 1 && (
12942                                         ( (pi.startStubX > pi.endStubX) && (pi.tx > pi.startStubX) ) ||
12943                                         ( (pi.sx > pi.endStubX) && (pi.tx > pi.sx))))) ||
12944 
12945                                         ( (pi.so[idx] === -1 && (
12946                                             ( (pi.startStubX < pi.endStubX) && (pi.tx < pi.startStubX) ) ||
12947                                             ( (pi.sx < pi.endStubX) && (pi.tx < pi.sx)))));
12948                                 },
12949                                 "y": function () {
12950                                     return ( (pi.so[idx] === 1 && (
12951                                         ( (pi.startStubY > pi.endStubY) && (pi.ty > pi.startStubY) ) ||
12952                                         ( (pi.sy > pi.endStubY) && (pi.ty > pi.sy))))) ||
12953 
12954                                         ( (pi.so[idx] === -1 && (
12955                                             ( (pi.startStubY < pi.endStubY) && (pi.ty < pi.startStubY) ) ||
12956                                             ( (pi.sy < pi.endStubY) && (pi.ty < pi.sy)))));
12957                                 }
12958                             };
12959 
12960                         if (!alwaysRespectStubs && areInProximity[axis]()) {
12961                             return {
12962                                 "x": [(paintInfo.sx + paintInfo.tx) / 2, paintInfo.startStubY, (paintInfo.sx + paintInfo.tx) / 2, paintInfo.endStubY],
12963                                 "y": [paintInfo.startStubX, (paintInfo.sy + paintInfo.ty) / 2, paintInfo.endStubX, (paintInfo.sy + paintInfo.ty) / 2]
12964                             }[axis];
12965                         }
12966                         else {
12967                             return [ paintInfo.startStubX, paintInfo.startStubY, paintInfo.endStubX, paintInfo.endStubY ];
12968                         }
12969                     }
12970                 };
12971 
12972             // calculate Stubs.
12973             var stubs = stubCalculators[paintInfo.anchorOrientation](paintInfo.sourceAxis),
12974                 idx = paintInfo.sourceAxis === "x" ? 0 : 1,
12975                 oidx = paintInfo.sourceAxis === "x" ? 1 : 0,
12976                 ss = stubs[idx],
12977                 oss = stubs[oidx],
12978                 es = stubs[idx + 2],
12979                 oes = stubs[oidx + 2];
12980 
12981             // add the start stub segment. use stubs for loopback as it will look better, with the loop spaced
12982             // away from the element.
12983             addSegment(segments, stubs[0], stubs[1], paintInfo);
12984 
12985             // if its a loopback and we should treat it differently.
12986             if (false &&params.sourcePos[0] === params.targetPos[0] && params.sourcePos[1] === params.targetPos[1]) {
12987 
12988                 // we use loopbackRadius here, as statemachine connectors do.
12989                 // so we go radius to the left from stubs[0], then upwards by 2*radius, to the right by 2*radius,
12990                 // down by 2*radius, left by radius.
12991                 addSegment(segments, stubs[0] - loopbackRadius, stubs[1], paintInfo);
12992                 addSegment(segments, stubs[0] - loopbackRadius, stubs[1] - (2 * loopbackRadius), paintInfo);
12993                 addSegment(segments, stubs[0] + loopbackRadius, stubs[1] - (2 * loopbackRadius), paintInfo);
12994                 addSegment(segments, stubs[0] + loopbackRadius, stubs[1], paintInfo);
12995                 addSegment(segments, stubs[0], stubs[1], paintInfo);
12996 
12997             }
12998             else {
12999 
13000 
13001                 var midx = paintInfo.startStubX + ((paintInfo.endStubX - paintInfo.startStubX) * midpoint),
13002                     midy = paintInfo.startStubY + ((paintInfo.endStubY - paintInfo.startStubY) * midpoint);
13003 
13004                 var orientations = { x: [ 0, 1 ], y: [ 1, 0 ] },
13005                     lineCalculators = {
13006                         perpendicular: function (axis) {
13007                             var pi = paintInfo,
13008                                 sis = {
13009                                     x: [
13010                                         [ [ 1, 2, 3, 4 ], null, [ 2, 1, 4, 3 ] ],
13011                                         null,
13012                                         [ [ 4, 3, 2, 1 ], null, [ 3, 4, 1, 2 ] ]
13013                                     ],
13014                                     y: [
13015                                         [ [ 3, 2, 1, 4 ], null, [ 2, 3, 4, 1 ] ],
13016                                         null,
13017                                         [ [ 4, 1, 2, 3 ], null, [ 1, 4, 3, 2 ] ]
13018                                     ]
13019                                 },
13020                                 stubs = {
13021                                     x: [ [ pi.startStubX, pi.endStubX ], null, [ pi.endStubX, pi.startStubX ] ],
13022                                     y: [ [ pi.startStubY, pi.endStubY ], null, [ pi.endStubY, pi.startStubY ] ]
13023                                 },
13024                                 midLines = {
13025                                     x: [ [ midx, pi.startStubY ], [ midx, pi.endStubY ] ],
13026                                     y: [ [ pi.startStubX, midy ], [ pi.endStubX, midy ] ]
13027                                 },
13028                                 linesToEnd = {
13029                                     x: [ [ pi.endStubX, pi.startStubY ] ],
13030                                     y: [ [ pi.startStubX, pi.endStubY ] ]
13031                                 },
13032                                 startToEnd = {
13033                                     x: [ [ pi.startStubX, pi.endStubY ], [ pi.endStubX, pi.endStubY ] ],
13034                                     y: [ [ pi.endStubX, pi.startStubY ], [ pi.endStubX, pi.endStubY ] ]
13035                                 },
13036                                 startToMidToEnd = {
13037                                     x: [ [ pi.startStubX, midy ], [ pi.endStubX, midy ], [ pi.endStubX, pi.endStubY ] ],
13038                                     y: [ [ midx, pi.startStubY ], [ midx, pi.endStubY ], [ pi.endStubX, pi.endStubY ] ]
13039                                 },
13040                                 otherStubs = {
13041                                     x: [ pi.startStubY, pi.endStubY ],
13042                                     y: [ pi.startStubX, pi.endStubX ]
13043                                 },
13044                                 soIdx = orientations[axis][0], toIdx = orientations[axis][1],
13045                                 _so = pi.so[soIdx] + 1,
13046                                 _to = pi.to[toIdx] + 1,
13047                                 otherFlipped = (pi.to[toIdx] === -1 && (otherStubs[axis][1] < otherStubs[axis][0])) || (pi.to[toIdx] === 1 && (otherStubs[axis][1] > otherStubs[axis][0])),
13048                                 stub1 = stubs[axis][_so][0],
13049                                 stub2 = stubs[axis][_so][1],
13050                                 segmentIndexes = sis[axis][_so][_to];
13051 
13052                             if (pi.segment === segmentIndexes[3] || (pi.segment === segmentIndexes[2] && otherFlipped)) {
13053                                 return midLines[axis];
13054                             }
13055                             else if (pi.segment === segmentIndexes[2] && stub2 < stub1) {
13056                                 return linesToEnd[axis];
13057                             }
13058                             else if ((pi.segment === segmentIndexes[2] && stub2 >= stub1) || (pi.segment === segmentIndexes[1] && !otherFlipped)) {
13059                                 return startToMidToEnd[axis];
13060                             }
13061                             else if (pi.segment === segmentIndexes[0] || (pi.segment === segmentIndexes[1] && otherFlipped)) {
13062                                 return startToEnd[axis];
13063                             }
13064                         },
13065                         orthogonal: function (axis, startStub, otherStartStub, endStub, otherEndStub) {
13066                             var pi = paintInfo,
13067                                 extent = {
13068                                     "x": pi.so[0] === -1 ? Math.min(startStub, endStub) : Math.max(startStub, endStub),
13069                                     "y": pi.so[1] === -1 ? Math.min(startStub, endStub) : Math.max(startStub, endStub)
13070                                 }[axis];
13071 
13072                             return {
13073                                 "x": [
13074                                     [ extent, otherStartStub ],
13075                                     [ extent, otherEndStub ],
13076                                     [ endStub, otherEndStub ]
13077                                 ],
13078                                 "y": [
13079                                     [ otherStartStub, extent ],
13080                                     [ otherEndStub, extent ],
13081                                     [ otherEndStub, endStub ]
13082                                 ]
13083                             }[axis];
13084                         },
13085                         opposite: function (axis, ss, oss, es) {
13086                             var pi = paintInfo,
13087                                 otherAxis = {"x": "y", "y": "x"}[axis],
13088                                 dim = {"x": "height", "y": "width"}[axis],
13089                                 comparator = pi["is" + axis.toUpperCase() + "GreaterThanStubTimes2"];
13090 
13091                             if (params.sourceEndpoint.elementId === params.targetEndpoint.elementId) {
13092                                 var _val = oss + ((1 - params.sourceEndpoint.anchor[otherAxis]) * params.sourceInfo[dim]) + _super.maxStub;
13093                                 return {
13094                                     "x": [
13095                                         [ ss, _val ],
13096                                         [ es, _val ]
13097                                     ],
13098                                     "y": [
13099                                         [ _val, ss ],
13100                                         [ _val, es ]
13101                                     ]
13102                                 }[axis];
13103 
13104                             }
13105                             else if (!comparator || (pi.so[idx] === 1 && ss > es) || (pi.so[idx] === -1 && ss < es)) {
13106                                 return {
13107                                     "x": [
13108                                         [ ss, midy ],
13109                                         [ es, midy ]
13110                                     ],
13111                                     "y": [
13112                                         [ midx, ss ],
13113                                         [ midx, es ]
13114                                     ]
13115                                 }[axis];
13116                             }
13117                             else if ((pi.so[idx] === 1 && ss < es) || (pi.so[idx] === -1 && ss > es)) {
13118                                 return {
13119                                     "x": [
13120                                         [ midx, pi.sy ],
13121                                         [ midx, pi.ty ]
13122                                     ],
13123                                     "y": [
13124                                         [ pi.sx, midy ],
13125                                         [ pi.tx, midy ]
13126                                     ]
13127                                 }[axis];
13128                             }
13129                         }
13130                     };
13131 
13132                 // compute the rest of the line
13133                 var p = lineCalculators[paintInfo.anchorOrientation](paintInfo.sourceAxis, ss, oss, es, oes);
13134                 if (p) {
13135                     for (var i = 0; i < p.length; i++) {
13136                         addSegment(segments, p[i][0], p[i][1], paintInfo);
13137                     }
13138                 }
13139 
13140                 // line to end stub
13141                 addSegment(segments, stubs[2], stubs[3], paintInfo);
13142 
13143             }
13144 
13145             // end stub to end (common)
13146             addSegment(segments, paintInfo.tx, paintInfo.ty, paintInfo);
13147 
13148             // write out the segments.
13149             writeSegments(this, segments, paintInfo);
13150         };
13151 
13152         /*this.getPath = function () {
13153             var _last = null, _lastAxis = null, s = [], segs = segments;
13154             for (var i = 0; i < segs.length; i++) {
13155                 var seg = segs[i], axis = seg[4], axisIndex = (axis == "v" ? 3 : 2);
13156                 if (_last != null && _lastAxis === axis) {
13157                     _last[axisIndex] = seg[axisIndex];
13158                 }
13159                 else {
13160                     if (seg[0] != seg[2] || seg[1] != seg[3]) {
13161                         s.push({
13162                             start: [ seg[0], seg[1] ],
13163                             end: [ seg[2], seg[3] ]
13164                         });
13165                         _last = seg;
13166                         _lastAxis = seg[4];
13167                     }
13168                 }
13169             }
13170             return s;
13171         };*/
13172     };
13173 
13174     _ju.extend(Flowchart, _jp.Connectors.AbstractConnector);
13175     _jp.registerConnectorType(Flowchart, "Flowchart");
13176 }).call(typeof window !== 'undefined' ? window : this);
13177 /*
13178  * jsPlumb Community Edition
13179  *
13180  * Provides a way to visually connect elements on an HTML page, using SVG.
13181  *
13182  * This file contains the code for the Bezier connector type.
13183  *
13184  * Copyright (c) 2010 - 2017 jsPlumb (hello@jsplumbtoolkit.com)
13185  *
13186  * https://jsplumbtoolkit.com
13187  * https://github.com/jsplumb/jsplumb
13188  * 
13189  * Dual licensed under the MIT and GPL2 licenses.
13190  */
13191 ;
13192 (function () {
13193 
13194     "use strict";
13195     var root = this, _jp = root.jsPlumb, _ju = root.jsPlumbUtil;
13196 
13197     _jp.Connectors.AbstractBezierConnector = function(params) {
13198         params = params || {};
13199         var showLoopback = params.showLoopback !== false,
13200             curviness = params.curviness || 10,
13201             margin = params.margin || 5,
13202             proximityLimit = params.proximityLimit || 80,
13203             clockwise = params.orientation && params.orientation === "clockwise",
13204             loopbackRadius = params.loopbackRadius || 25,
13205             isLoopbackCurrently = false,
13206             _super;
13207 
13208         this.overrideSetEditable = function() { return !isLoopbackCurrently; };
13209 
13210         this._compute = function (paintInfo, p) {
13211 
13212             var sp = p.sourcePos,
13213                 tp = p.targetPos,
13214                 _w = Math.abs(sp[0] - tp[0]),
13215                 _h = Math.abs(sp[1] - tp[1]);
13216 
13217             if (!showLoopback || (p.sourceEndpoint.elementId !== p.targetEndpoint.elementId)) {
13218                 isLoopbackCurrently = false;
13219                 this._computeBezier(paintInfo, p, sp, tp, _w, _h);
13220             } else {
13221                 isLoopbackCurrently = true;
13222                 // a loopback connector.  draw an arc from one anchor to the other.
13223                 var x1 = p.sourcePos[0], y1 = p.sourcePos[1] - margin,
13224                     cx = x1, cy = y1 - loopbackRadius,
13225                 // canvas sizing stuff, to ensure the whole painted area is visible.
13226                     _x = cx - loopbackRadius,
13227                     _y = cy - loopbackRadius;
13228 
13229                 _w = 2 * loopbackRadius;
13230                 _h = 2 * loopbackRadius;
13231 
13232                 paintInfo.points[0] = _x;
13233                 paintInfo.points[1] = _y;
13234                 paintInfo.points[2] = _w;
13235                 paintInfo.points[3] = _h;
13236 
13237                 // ADD AN ARC SEGMENT.
13238                 _super.addSegment(this, "Arc", {
13239                     loopback: true,
13240                     x1: (x1 - _x) + 4,
13241                     y1: y1 - _y,
13242                     startAngle: 0,
13243                     endAngle: 2 * Math.PI,
13244                     r: loopbackRadius,
13245                     ac: !clockwise,
13246                     x2: (x1 - _x) - 4,
13247                     y2: y1 - _y,
13248                     cx: cx - _x,
13249                     cy: cy - _y
13250                 });
13251             }
13252         };
13253 
13254         _super = _jp.Connectors.AbstractConnector.apply(this, arguments);
13255         return _super;
13256     };
13257     _ju.extend(_jp.Connectors.AbstractBezierConnector, _jp.Connectors.AbstractConnector);
13258 
13259     var Bezier = function (params) {
13260         params = params || {};
13261         this.type = "Bezier";
13262 
13263         var _super = _jp.Connectors.AbstractBezierConnector.apply(this, arguments),
13264             majorAnchor = params.curviness || 150,
13265             minorAnchor = 10;
13266 
13267         this.getCurviness = function () {
13268             return majorAnchor;
13269         };
13270 
13271         this._findControlPoint = function (point, sourceAnchorPosition, targetAnchorPosition, sourceEndpoint, targetEndpoint, soo, too) {
13272             // determine if the two anchors are perpendicular to each other in their orientation.  we swap the control
13273             // points around if so (code could be tightened up)
13274             var perpendicular = soo[0] !== too[0] || soo[1] === too[1],
13275                 p = [];
13276 
13277             if (!perpendicular) {
13278                 if (soo[0] === 0) {
13279                     p.push(sourceAnchorPosition[0] < targetAnchorPosition[0] ? point[0] + minorAnchor : point[0] - minorAnchor);
13280                 }
13281                 else {
13282                     p.push(point[0] - (majorAnchor * soo[0]));
13283                 }
13284 
13285                 if (soo[1] === 0) {
13286                     p.push(sourceAnchorPosition[1] < targetAnchorPosition[1] ? point[1] + minorAnchor : point[1] - minorAnchor);
13287                 }
13288                 else {
13289                     p.push(point[1] + (majorAnchor * too[1]));
13290                 }
13291             }
13292             else {
13293                 if (too[0] === 0) {
13294                     p.push(targetAnchorPosition[0] < sourceAnchorPosition[0] ? point[0] + minorAnchor : point[0] - minorAnchor);
13295                 }
13296                 else {
13297                     p.push(point[0] + (majorAnchor * too[0]));
13298                 }
13299 
13300                 if (too[1] === 0) {
13301                     p.push(targetAnchorPosition[1] < sourceAnchorPosition[1] ? point[1] + minorAnchor : point[1] - minorAnchor);
13302                 }
13303                 else {
13304                     p.push(point[1] + (majorAnchor * soo[1]));
13305                 }
13306             }
13307 
13308             return p;
13309         };
13310 
13311         this._computeBezier = function (paintInfo, p, sp, tp, _w, _h) {
13312 
13313             var geometry = this.getGeometry(), _CP, _CP2,
13314                 _sx = sp[0] < tp[0] ? _w : 0,
13315                 _sy = sp[1] < tp[1] ? _h : 0,
13316                 _tx = sp[0] < tp[0] ? 0 : _w,
13317                 _ty = sp[1] < tp[1] ? 0 : _h;
13318 
13319             if ((this.hasBeenEdited() || this.isEditing()) && geometry != null && geometry.controlPoints != null && geometry.controlPoints[0] != null && geometry.controlPoints[1] != null) {
13320                 _CP = geometry.controlPoints[0];
13321                 _CP2 = geometry.controlPoints[1];
13322             }
13323             else {
13324                 _CP = this._findControlPoint([_sx, _sy], sp, tp, p.sourceEndpoint, p.targetEndpoint, paintInfo.so, paintInfo.to);
13325                 _CP2 = this._findControlPoint([_tx, _ty], tp, sp, p.targetEndpoint, p.sourceEndpoint, paintInfo.to, paintInfo.so);
13326             }
13327 
13328             _super.setGeometry({controlPoints:[_CP, _CP2]}, true);
13329 
13330             _super.addSegment(this, "Bezier", {
13331                 x1: _sx, y1: _sy, x2: _tx, y2: _ty,
13332                 cp1x: _CP[0], cp1y: _CP[1], cp2x: _CP2[0], cp2y: _CP2[1]
13333             });
13334         };
13335 
13336 
13337     };
13338 
13339     _ju.extend(Bezier, _jp.Connectors.AbstractBezierConnector);
13340     _jp.registerConnectorType(Bezier, "Bezier");
13341 
13342 }).call(typeof window !== 'undefined' ? window : this);
13343 /*
13344  * jsPlumb Community Edition
13345  *
13346  * Provides a way to visually connect elements on an HTML page, using SVG.
13347  *
13348  * This file contains the state machine connectors, which extend AbstractBezierConnector.
13349  *
13350  * Copyright (c) 2010 - 2017 jsPlumb (hello@jsplumbtoolkit.com)
13351  *
13352  * https://jsplumbtoolkit.com
13353  * https://github.com/jsplumb/jsplumb
13354  * 
13355  * Dual licensed under the MIT and GPL2 licenses.
13356  */
13357 ;
13358 (function () {
13359 
13360     "use strict";
13361     var root = this, _jp = root.jsPlumb, _ju = root.jsPlumbUtil;
13362 
13363     var _segment = function (x1, y1, x2, y2) {
13364             if (x1 <= x2 && y2 <= y1) {
13365                 return 1;
13366             }
13367             else if (x1 <= x2 && y1 <= y2) {
13368                 return 2;
13369             }
13370             else if (x2 <= x1 && y2 >= y1) {
13371                 return 3;
13372             }
13373             return 4;
13374         },
13375 
13376     // the control point we will use depends on the faces to which each end of the connection is assigned, specifically whether or not the
13377     // two faces are parallel or perpendicular.  if they are parallel then the control point lies on the midpoint of the axis in which they
13378     // are parellel and varies only in the other axis; this variation is proportional to the distance that the anchor points lie from the
13379     // center of that face.  if the two faces are perpendicular then the control point is at some distance from both the midpoints; the amount and
13380     // direction are dependent on the orientation of the two elements. 'seg', passed in to this method, tells you which segment the target element
13381     // lies in with respect to the source: 1 is top right, 2 is bottom right, 3 is bottom left, 4 is top left.
13382     //
13383     // sourcePos and targetPos are arrays of info about where on the source and target each anchor is located.  their contents are:
13384     //
13385     // 0 - absolute x
13386     // 1 - absolute y
13387     // 2 - proportional x in element (0 is left edge, 1 is right edge)
13388     // 3 - proportional y in element (0 is top edge, 1 is bottom edge)
13389     //
13390         _findControlPoint = function (midx, midy, segment, sourceEdge, targetEdge, dx, dy, distance, proximityLimit) {
13391             // TODO (maybe)
13392             // - if anchor pos is 0.5, make the control point take into account the relative position of the elements.
13393             if (distance <= proximityLimit) {
13394                 return [midx, midy];
13395             }
13396 
13397             if (segment === 1) {
13398                 if (sourceEdge[3] <= 0 && targetEdge[3] >= 1) {
13399                     return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ];
13400                 }
13401                 else if (sourceEdge[2] >= 1 && targetEdge[2] <= 0) {
13402                     return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ];
13403                 }
13404                 else {
13405                     return [ midx + (-1 * dx) , midy + (-1 * dy) ];
13406                 }
13407             }
13408             else if (segment === 2) {
13409                 if (sourceEdge[3] >= 1 && targetEdge[3] <= 0) {
13410                     return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ];
13411                 }
13412                 else if (sourceEdge[2] >= 1 && targetEdge[2] <= 0) {
13413                     return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ];
13414                 }
13415                 else {
13416                     return [ midx + dx, midy + (-1 * dy) ];
13417                 }
13418             }
13419             else if (segment === 3) {
13420                 if (sourceEdge[3] >= 1 && targetEdge[3] <= 0) {
13421                     return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ];
13422                 }
13423                 else if (sourceEdge[2] <= 0 && targetEdge[2] >= 1) {
13424                     return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ];
13425                 }
13426                 else {
13427                     return [ midx + (-1 * dx) , midy + (-1 * dy) ];
13428                 }
13429             }
13430             else if (segment === 4) {
13431                 if (sourceEdge[3] <= 0 && targetEdge[3] >= 1) {
13432                     return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ];
13433                 }
13434                 else if (sourceEdge[2] <= 0 && targetEdge[2] >= 1) {
13435                     return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ];
13436                 }
13437                 else {
13438                     return [ midx + dx , midy + (-1 * dy) ];
13439                 }
13440             }
13441 
13442         };
13443 
13444     var StateMachine = function (params) {
13445         params = params || {};
13446         this.type = "StateMachine";
13447 
13448         var _super = _jp.Connectors.AbstractBezierConnector.apply(this, arguments),
13449             curviness = params.curviness || 10,
13450             margin = params.margin || 5,
13451             proximityLimit = params.proximityLimit || 80,
13452             clockwise = params.orientation && params.orientation === "clockwise",
13453             _controlPoint;
13454 
13455         this._computeBezier = function(paintInfo, params, sp, tp, w, h) {
13456             var _sx = params.sourcePos[0] < params.targetPos[0] ? 0 : w,
13457                 _sy = params.sourcePos[1] < params.targetPos[1] ? 0 : h,
13458                 _tx = params.sourcePos[0] < params.targetPos[0] ? w : 0,
13459                 _ty = params.sourcePos[1] < params.targetPos[1] ? h : 0;
13460 
13461             // now adjust for the margin
13462             if (params.sourcePos[2] === 0) {
13463                 _sx -= margin;
13464             }
13465             if (params.sourcePos[2] === 1) {
13466                 _sx += margin;
13467             }
13468             if (params.sourcePos[3] === 0) {
13469                 _sy -= margin;
13470             }
13471             if (params.sourcePos[3] === 1) {
13472                 _sy += margin;
13473             }
13474             if (params.targetPos[2] === 0) {
13475                 _tx -= margin;
13476             }
13477             if (params.targetPos[2] === 1) {
13478                 _tx += margin;
13479             }
13480             if (params.targetPos[3] === 0) {
13481                 _ty -= margin;
13482             }
13483             if (params.targetPos[3] === 1) {
13484                 _ty += margin;
13485             }
13486 
13487             //
13488             // these connectors are quadratic bezier curves, having a single control point. if both anchors
13489             // are located at 0.5 on their respective faces, the control point is set to the midpoint and you
13490             // get a straight line.  this is also the case if the two anchors are within 'proximityLimit', since
13491             // it seems to make good aesthetic sense to do that. outside of that, the control point is positioned
13492             // at 'curviness' pixels away along the normal to the straight line connecting the two anchors.
13493             //
13494             // there may be two improvements to this.  firstly, we might actually support the notion of avoiding nodes
13495             // in the UI, or at least making a good effort at doing so.  if a connection would pass underneath some node,
13496             // for example, we might increase the distance the control point is away from the midpoint in a bid to
13497             // steer it around that node.  this will work within limits, but i think those limits would also be the likely
13498             // limits for, once again, aesthetic good sense in the layout of a chart using these connectors.
13499             //
13500             // the second possible change is actually two possible changes: firstly, it is possible we should gradually
13501             // decrease the 'curviness' as the distance between the anchors decreases; start tailing it off to 0 at some
13502             // point (which should be configurable).  secondly, we might slightly increase the 'curviness' for connectors
13503             // with respect to how far their anchor is from the center of its respective face. this could either look cool,
13504             // or stupid, and may indeed work only in a way that is so subtle as to have been a waste of time.
13505             //
13506 
13507             var _midx = (_sx + _tx) / 2,
13508                 _midy = (_sy + _ty) / 2,
13509                 segment = _segment(_sx, _sy, _tx, _ty),
13510                 distance = Math.sqrt(Math.pow(_tx - _sx, 2) + Math.pow(_ty - _sy, 2)),
13511                 cp1x, cp2x, cp1y, cp2y,
13512                 geometry = _super.getGeometry();
13513 
13514             if ((this.hasBeenEdited() || this.isEditing()) && geometry != null) {
13515                 cp1x = geometry.controlPoints[0][0];
13516                 cp1y = geometry.controlPoints[0][1];
13517                 cp2x = geometry.controlPoints[1][0];
13518                 cp2y = geometry.controlPoints[1][1];
13519             }
13520             else {
13521                 // calculate the control point.  this code will be where we'll put in a rudimentary element avoidance scheme; it
13522                 // will work by extending the control point to force the curve to be, um, curvier.
13523                 _controlPoint = _findControlPoint(_midx,
13524                     _midy,
13525                     segment,
13526                     params.sourcePos,
13527                     params.targetPos,
13528                     curviness, curviness,
13529                     distance,
13530                     proximityLimit);
13531 
13532                 cp1x = _controlPoint[0];
13533                 cp2x = _controlPoint[0];
13534                 cp1y = _controlPoint[1];
13535                 cp2y = _controlPoint[1];
13536 
13537                 _super.setGeometry({controlPoints:[_controlPoint, _controlPoint]}, true);
13538             }
13539 
13540             _super.addSegment(this, "Bezier", {
13541                 x1: _tx, y1: _ty, x2: _sx, y2: _sy,
13542                 cp1x: cp1x, cp1y: cp1y,
13543                 cp2x: cp2x, cp2y: cp2y
13544             });
13545         };
13546     };
13547 
13548     _ju.extend(StateMachine, _jp.Connectors.AbstractBezierConnector);
13549     _jp.registerConnectorType(StateMachine, "StateMachine");
13550 
13551 }).call(typeof window !== 'undefined' ? window : this);
13552 /*
13553  * jsPlumb Community Edition
13554  *
13555  * Provides a way to visually connect elements on an HTML page, using SVG.
13556  *
13557  * This file contains the 'flowchart' connectors, consisting of vertical and horizontal line segments.
13558  *
13559  * Copyright (c) 2010 - 2017 jsPlumb (hello@jsplumbtoolkit.com)
13560  *
13561  * https://jsplumbtoolkit.com
13562  * https://github.com/jsplumb/jsplumb
13563  *
13564  * Dual licensed under the MIT and GPL2 licenses.
13565  */
13566 ;
13567 (function () {
13568 
13569     "use strict";
13570     var root = this, _jp = root.jsPlumb, _ju = root.jsPlumbUtil;
13571     var STRAIGHT = "Straight";
13572 
13573     var Straight = function (params) {
13574         this.type = STRAIGHT;
13575         var _super = _jp.Connectors.AbstractConnector.apply(this, arguments);
13576 
13577         this._compute = function (paintInfo, _) {
13578             _super.addSegment(this, STRAIGHT, {x1: paintInfo.sx, y1: paintInfo.sy, x2: paintInfo.startStubX, y2: paintInfo.startStubY});
13579             _super.addSegment(this, STRAIGHT, {x1: paintInfo.startStubX, y1: paintInfo.startStubY, x2: paintInfo.endStubX, y2: paintInfo.endStubY});
13580             _super.addSegment(this, STRAIGHT, {x1: paintInfo.endStubX, y1: paintInfo.endStubY, x2: paintInfo.tx, y2: paintInfo.ty});
13581         };
13582     };
13583 
13584     _ju.extend(Straight, _jp.Connectors.AbstractConnector);
13585     _jp.registerConnectorType(Straight, STRAIGHT);
13586 
13587 }).call(typeof window !== 'undefined' ? window : this);
13588 /*
13589  * jsPlumb Community Edition
13590  * 
13591  * Provides a way to visually connect elements on an HTML page, using SVG.
13592  * 
13593  * This file contains the SVG renderers.
13594  *
13595  * Copyright (c) 2010 - 2017 jsPlumb (hello@jsplumbtoolkit.com)
13596  * 
13597  * https://jsplumbtoolkit.com
13598  * https://github.com/jsplumb/jsplumb
13599  * 
13600  * Dual licensed under the MIT and GPL2 licenses.
13601  */
13602 ;
13603 (function () {
13604 
13605 // ************************** SVG utility methods ********************************************	
13606 
13607     "use strict";
13608     var root = this, _jp = root.jsPlumb, _ju = root.jsPlumbUtil;
13609 
13610     var svgAttributeMap = {
13611             "stroke-linejoin": "stroke-linejoin",
13612             "stroke-dashoffset": "stroke-dashoffset",
13613             "stroke-linecap": "stroke-linecap"
13614         },
13615         STROKE_DASHARRAY = "stroke-dasharray",
13616         DASHSTYLE = "dashstyle",
13617         LINEAR_GRADIENT = "linearGradient",
13618         RADIAL_GRADIENT = "radialGradient",
13619         DEFS = "defs",
13620         FILL = "fill",
13621         STOP = "stop",
13622         STROKE = "stroke",
13623         STROKE_WIDTH = "stroke-width",
13624         STYLE = "style",
13625         NONE = "none",
13626         JSPLUMB_GRADIENT = "jsplumb_gradient_",
13627         LINE_WIDTH = "strokeWidth",
13628         ns = {
13629             svg: "http://www.w3.org/2000/svg"
13630         },
13631         _attr = function (node, attributes) {
13632             for (var i in attributes) {
13633                 node.setAttribute(i, "" + attributes[i]);
13634             }
13635         },
13636         _node = function (name, attributes) {
13637             attributes = attributes || {};
13638             attributes.version = "1.1";
13639             attributes.xmlns = ns.svg;
13640             return _jp.createElementNS(ns.svg, name, null, null, attributes);
13641         },
13642         _pos = function (d) {
13643             return "position:absolute;left:" + d[0] + "px;top:" + d[1] + "px";
13644         },
13645         _clearGradient = function (parent) {
13646             var els = parent.querySelectorAll(" defs,linearGradient,radialGradient");
13647             for (var i = 0; i < els.length; i++) {
13648                 els[i].parentNode.removeChild(els[i]);
13649             }
13650         },
13651         _updateGradient = function (parent, node, style, dimensions, uiComponent) {
13652             var id = JSPLUMB_GRADIENT + uiComponent._jsPlumb.instance.idstamp();
13653             // first clear out any existing gradient
13654             _clearGradient(parent);
13655             // this checks for an 'offset' property in the gradient, and in the absence of it, assumes
13656             // we want a linear gradient. if it's there, we create a radial gradient.
13657             // it is possible that a more explicit means of defining the gradient type would be
13658             // better. relying on 'offset' means that we can never have a radial gradient that uses
13659             // some default offset, for instance.
13660             // issue 244 suggested the 'gradientUnits' attribute; without this, straight/flowchart connectors with gradients would
13661             // not show gradients when the line was perfectly horizontal or vertical.
13662             var g;
13663             if (!style.gradient.offset) {
13664                 g = _node(LINEAR_GRADIENT, {id: id, gradientUnits: "userSpaceOnUse"});
13665             }
13666             else {
13667                 g = _node(RADIAL_GRADIENT, { id: id });
13668             }
13669 
13670             var defs = _node(DEFS);
13671             parent.appendChild(defs);
13672             defs.appendChild(g);
13673 
13674             // the svg radial gradient seems to treat stops in the reverse
13675             // order to how canvas does it.  so we want to keep all the maths the same, but
13676             // iterate the actual style declarations in reverse order, if the x indexes are not in order.
13677             for (var i = 0; i < style.gradient.stops.length; i++) {
13678                 var styleToUse = uiComponent.segment === 1 || uiComponent.segment === 2 ? i : style.gradient.stops.length - 1 - i,
13679                     stopColor = style.gradient.stops[styleToUse][1],
13680                     s = _node(STOP, {"offset": Math.floor(style.gradient.stops[i][0] * 100) + "%", "stop-color": stopColor});
13681 
13682                 g.appendChild(s);
13683             }
13684             var applyGradientTo = style.stroke ? STROKE : FILL;
13685             node.setAttribute(applyGradientTo, "url(#" + id + ")");
13686         },
13687         _applyStyles = function (parent, node, style, dimensions, uiComponent) {
13688 
13689             node.setAttribute(FILL, style.fill ? style.fill : NONE);
13690             node.setAttribute(STROKE, style.stroke ? style.stroke : NONE);
13691 
13692             if (style.gradient) {
13693                 _updateGradient(parent, node, style, dimensions, uiComponent);
13694             }
13695             else {
13696                 // make sure we clear any existing gradient
13697                 _clearGradient(parent);
13698                 node.setAttribute(STYLE, "");
13699             }
13700 
13701             if (style.strokeWidth) {
13702                 node.setAttribute(STROKE_WIDTH, style.strokeWidth);
13703             }
13704 
13705             // in SVG there is a stroke-dasharray attribute we can set, and its syntax looks like
13706             // the syntax in VML but is actually kind of nasty: values are given in the pixel
13707             // coordinate space, whereas in VML they are multiples of the width of the stroked
13708             // line, which makes a lot more sense.  for that reason, jsPlumb is supporting both
13709             // the native svg 'stroke-dasharray' attribute, and also the 'dashstyle' concept from
13710             // VML, which will be the preferred method.  the code below this converts a dashstyle
13711             // attribute given in terms of stroke width into a pixel representation, by using the
13712             // stroke's lineWidth.
13713             if (style[DASHSTYLE] && style[LINE_WIDTH] && !style[STROKE_DASHARRAY]) {
13714                 var sep = style[DASHSTYLE].indexOf(",") === -1 ? " " : ",",
13715                     parts = style[DASHSTYLE].split(sep),
13716                     styleToUse = "";
13717                 parts.forEach(function (p) {
13718                     styleToUse += (Math.floor(p * style.strokeWidth) + sep);
13719                 });
13720                 node.setAttribute(STROKE_DASHARRAY, styleToUse);
13721             }
13722             else if (style[STROKE_DASHARRAY]) {
13723                 node.setAttribute(STROKE_DASHARRAY, style[STROKE_DASHARRAY]);
13724             }
13725 
13726             // extra attributes such as join type, dash offset.
13727             for (var i in svgAttributeMap) {
13728                 if (style[i]) {
13729                     node.setAttribute(svgAttributeMap[i], style[i]);
13730                 }
13731             }
13732         },
13733         _appendAtIndex = function (svg, path, idx) {
13734             if (svg.childNodes.length > idx) {
13735                 svg.insertBefore(path, svg.childNodes[idx]);
13736             }
13737             else {
13738                 svg.appendChild(path);
13739             }
13740         };
13741 
13742     /**
13743      utility methods for other objects to use.
13744      */
13745     _ju.svg = {
13746         node: _node,
13747         attr: _attr,
13748         pos: _pos
13749     };
13750 
13751     // ************************** / SVG utility methods ********************************************
13752 
13753     /*
13754      * Base class for SVG components.
13755      */
13756     var SvgComponent = function (params) {
13757         var pointerEventsSpec = params.pointerEventsSpec || "all", renderer = {};
13758 
13759         _jp.jsPlumbUIComponent.apply(this, params.originalArgs);
13760         this.canvas = null;
13761         this.path = null;
13762         this.svg = null;
13763         this.bgCanvas = null;
13764 
13765         var clazz = params.cssClass + " " + (params.originalArgs[0].cssClass || ""),
13766             svgParams = {
13767                 "style": "",
13768                 "width": 0,
13769                 "height": 0,
13770                 "pointer-events": pointerEventsSpec,
13771                 "position": "absolute"
13772             };
13773 
13774         this.svg = _node("svg", svgParams);
13775 
13776         if (params.useDivWrapper) {
13777             this.canvas = _jp.createElement("div", { position : "absolute" });
13778             _ju.sizeElement(this.canvas, 0, 0, 1, 1);
13779             this.canvas.className = clazz;
13780         }
13781         else {
13782             _attr(this.svg, { "class": clazz });
13783             this.canvas = this.svg;
13784         }
13785 
13786         params._jsPlumb.appendElement(this.canvas, params.originalArgs[0].parent);
13787         if (params.useDivWrapper) {
13788             this.canvas.appendChild(this.svg);
13789         }
13790 
13791         var displayElements = [ this.canvas ];
13792         this.getDisplayElements = function () {
13793             return displayElements;
13794         };
13795 
13796         this.appendDisplayElement = function (el) {
13797             displayElements.push(el);
13798         };
13799 
13800         this.paint = function (style, anchor, extents) {
13801             if (style != null) {
13802 
13803                 var xy = [ this.x, this.y ], wh = [ this.w, this.h ], p;
13804                 if (extents != null) {
13805                     if (extents.xmin < 0) {
13806                         xy[0] += extents.xmin;
13807                     }
13808                     if (extents.ymin < 0) {
13809                         xy[1] += extents.ymin;
13810                     }
13811                     wh[0] = extents.xmax + ((extents.xmin < 0) ? -extents.xmin : 0);
13812                     wh[1] = extents.ymax + ((extents.ymin < 0) ? -extents.ymin : 0);
13813                 }
13814 
13815                 if (params.useDivWrapper) {
13816                     _ju.sizeElement(this.canvas, xy[0], xy[1], wh[0], wh[1]);
13817                     xy[0] = 0;
13818                     xy[1] = 0;
13819                     p = _pos([ 0, 0 ]);
13820                 }
13821                 else {
13822                     p = _pos([ xy[0], xy[1] ]);
13823                 }
13824 
13825                 renderer.paint.apply(this, arguments);
13826 
13827                 _attr(this.svg, {
13828                     "style": p,
13829                     "width": wh[0] || 0,
13830                     "height": wh[1] || 0
13831                 });
13832             }
13833         };
13834 
13835         return {
13836             renderer: renderer
13837         };
13838     };
13839 
13840     _ju.extend(SvgComponent, _jp.jsPlumbUIComponent, {
13841         cleanup: function (force) {
13842             if (force || this.typeId == null) {
13843                 if (this.canvas) {
13844                     this.canvas._jsPlumb = null;
13845                 }
13846                 if (this.svg) {
13847                     this.svg._jsPlumb = null;
13848                 }
13849                 if (this.bgCanvas) {
13850                     this.bgCanvas._jsPlumb = null;
13851                 }
13852 
13853                 if (this.canvas && this.canvas.parentNode) {
13854                     this.canvas.parentNode.removeChild(this.canvas);
13855                 }
13856                 if (this.bgCanvas && this.bgCanvas.parentNode) {
13857                     this.canvas.parentNode.removeChild(this.canvas);
13858                 }
13859 
13860                 this.svg = null;
13861                 this.canvas = null;
13862                 this.path = null;
13863                 this.group = null;
13864             }
13865             else {
13866                 // if not a forced cleanup, just detach from DOM for now.
13867                 if (this.canvas && this.canvas.parentNode) {
13868                     this.canvas.parentNode.removeChild(this.canvas);
13869                 }
13870                 if (this.bgCanvas && this.bgCanvas.parentNode) {
13871                     this.bgCanvas.parentNode.removeChild(this.bgCanvas);
13872                 }
13873             }
13874         },
13875         reattach:function(instance) {
13876             var c = instance.getContainer();
13877             if (this.canvas && this.canvas.parentNode == null) {
13878                 c.appendChild(this.canvas);
13879             }
13880             if (this.bgCanvas && this.bgCanvas.parentNode == null) {
13881                 c.appendChild(this.bgCanvas);
13882             }
13883         },
13884         setVisible: function (v) {
13885             if (this.canvas) {
13886                 this.canvas.style.display = v ? "block" : "none";
13887             }
13888         }
13889     });
13890 
13891     /*
13892      * Base class for SVG connectors.
13893      */
13894     _jp.ConnectorRenderers.svg = function (params) {
13895         var self = this,
13896             _super = SvgComponent.apply(this, [
13897                 {
13898                     cssClass: params._jsPlumb.connectorClass + (this.isEditable() ? " " + params._jsPlumb.editableConnectorClass : ""),
13899                     originalArgs: arguments,
13900                     pointerEventsSpec: "none",
13901                     _jsPlumb: params._jsPlumb
13902                 }
13903             ]);
13904 
13905         var _superSetEditable = this.setEditable;
13906         this.setEditable = function(e) {
13907             var result = _superSetEditable.apply(this, [e]);
13908             _jp[result ? "addClass" : "removeClass"](this.canvas, this._jsPlumb.instance.editableConnectorClass);
13909         };
13910 
13911         _super.renderer.paint = function (style, anchor, extents) {
13912 
13913             var segments = self.getSegments(), p = "", offset = [0, 0];
13914             if (extents.xmin < 0) {
13915                 offset[0] = -extents.xmin;
13916             }
13917             if (extents.ymin < 0) {
13918                 offset[1] = -extents.ymin;
13919             }
13920 
13921             if (segments.length > 0) {
13922 
13923                 p = self.getPathData();
13924 
13925                 var a = {
13926                         d: p,
13927                         transform: "translate(" + offset[0] + "," + offset[1] + ")",
13928                         "pointer-events": params["pointer-events"] || "visibleStroke"
13929                     },
13930                     outlineStyle = null,
13931                     d = [self.x, self.y, self.w, self.h];
13932 
13933                 // outline style.  actually means drawing an svg object underneath the main one.
13934                 if (style.outlineStroke) {
13935                     var outlineWidth = style.outlineWidth || 1,
13936                         outlineStrokeWidth = style.strokeWidth + (2 * outlineWidth);
13937                     outlineStyle = _jp.extend({}, style);
13938                     delete outlineStyle.gradient;
13939                     outlineStyle.stroke = style.outlineStroke;
13940                     outlineStyle.strokeWidth = outlineStrokeWidth;
13941 
13942                     if (self.bgPath == null) {
13943                         self.bgPath = _node("path", a);
13944                         _jp.addClass(self.bgPath, _jp.connectorOutlineClass);
13945                         _appendAtIndex(self.svg, self.bgPath, 0);
13946                     }
13947                     else {
13948                         _attr(self.bgPath, a);
13949                     }
13950 
13951                     _applyStyles(self.svg, self.bgPath, outlineStyle, d, self);
13952                 }
13953 
13954                 if (self.path == null) {
13955                     self.path = _node("path", a);
13956                     _appendAtIndex(self.svg, self.path, style.outlineStroke ? 1 : 0);
13957                 }
13958                 else {
13959                     _attr(self.path, a);
13960                 }
13961 
13962                 _applyStyles(self.svg, self.path, style, d, self);
13963             }
13964         };
13965     };
13966     _ju.extend(_jp.ConnectorRenderers.svg, SvgComponent);
13967 
13968 // ******************************* svg segment renderer *****************************************************	
13969 
13970 
13971 // ******************************* /svg segments *****************************************************
13972 
13973     /*
13974      * Base class for SVG endpoints.
13975      */
13976     var SvgEndpoint = _jp.SvgEndpoint = function (params) {
13977         var _super = SvgComponent.apply(this, [
13978             {
13979                 cssClass: params._jsPlumb.endpointClass,
13980                 originalArgs: arguments,
13981                 pointerEventsSpec: "all",
13982                 useDivWrapper: true,
13983                 _jsPlumb: params._jsPlumb
13984             }
13985         ]);
13986 
13987         _super.renderer.paint = function (style) {
13988             var s = _jp.extend({}, style);
13989             if (s.outlineStroke) {
13990                 s.stroke = s.outlineStroke;
13991             }
13992 
13993             if (this.node == null) {
13994                 this.node = this.makeNode(s);
13995                 this.svg.appendChild(this.node);
13996             }
13997             else if (this.updateNode != null) {
13998                 this.updateNode(this.node);
13999             }
14000             _applyStyles(this.svg, this.node, s, [ this.x, this.y, this.w, this.h ], this);
14001             _pos(this.node, [ this.x, this.y ]);
14002         }.bind(this);
14003 
14004     };
14005     _ju.extend(SvgEndpoint, SvgComponent);
14006 
14007     /*
14008      * SVG Dot Endpoint
14009      */
14010     _jp.Endpoints.svg.Dot = function () {
14011         _jp.Endpoints.Dot.apply(this, arguments);
14012         SvgEndpoint.apply(this, arguments);
14013         this.makeNode = function (style) {
14014             return _node("circle", {
14015                 "cx": this.w / 2,
14016                 "cy": this.h / 2,
14017                 "r": this.radius
14018             });
14019         };
14020         this.updateNode = function (node) {
14021             _attr(node, {
14022                 "cx": this.w / 2,
14023                 "cy": this.h / 2,
14024                 "r": this.radius
14025             });
14026         };
14027     };
14028     _ju.extend(_jp.Endpoints.svg.Dot, [_jp.Endpoints.Dot, SvgEndpoint]);
14029 
14030     /*
14031      * SVG Rectangle Endpoint
14032      */
14033     _jp.Endpoints.svg.Rectangle = function () {
14034         _jp.Endpoints.Rectangle.apply(this, arguments);
14035         SvgEndpoint.apply(this, arguments);
14036         this.makeNode = function (style) {
14037             return _node("rect", {
14038                 "width": this.w,
14039                 "height": this.h
14040             });
14041         };
14042         this.updateNode = function (node) {
14043             _attr(node, {
14044                 "width": this.w,
14045                 "height": this.h
14046             });
14047         };
14048     };
14049     _ju.extend(_jp.Endpoints.svg.Rectangle, [_jp.Endpoints.Rectangle, SvgEndpoint]);
14050 
14051     /*
14052      * SVG Image Endpoint is the default image endpoint.
14053      */
14054     _jp.Endpoints.svg.Image = _jp.Endpoints.Image;
14055     /*
14056      * Blank endpoint in svg renderer is the default Blank endpoint.
14057      */
14058     _jp.Endpoints.svg.Blank = _jp.Endpoints.Blank;
14059     /*
14060      * Label overlay in svg renderer is the default Label overlay.
14061      */
14062     _jp.Overlays.svg.Label = _jp.Overlays.Label;
14063     /*
14064      * Custom overlay in svg renderer is the default Custom overlay.
14065      */
14066     _jp.Overlays.svg.Custom = _jp.Overlays.Custom;
14067 
14068     var AbstractSvgArrowOverlay = function (superclass, originalArgs) {
14069         superclass.apply(this, originalArgs);
14070         _jp.jsPlumbUIComponent.apply(this, originalArgs);
14071         this.isAppendedAtTopLevel = false;
14072         var self = this;
14073         this.path = null;
14074         this.paint = function (params, containerExtents) {
14075             // only draws on connections, not endpoints.
14076             if (params.component.svg && containerExtents) {
14077                 if (this.path == null) {
14078                     this.path = _node("path", {
14079                         "pointer-events": "all"
14080                     });
14081                     params.component.svg.appendChild(this.path);
14082                     if (this.elementCreated) {
14083                         this.elementCreated(this.path, params.component);
14084                     }
14085 
14086                     this.canvas = params.component.svg; // for the sake of completeness; this behaves the same as other overlays
14087                 }
14088                 var clazz = originalArgs && (originalArgs.length === 1) ? (originalArgs[0].cssClass || "") : "",
14089                     offset = [0, 0];
14090 
14091                 if (containerExtents.xmin < 0) {
14092                     offset[0] = -containerExtents.xmin;
14093                 }
14094                 if (containerExtents.ymin < 0) {
14095                     offset[1] = -containerExtents.ymin;
14096                 }
14097 
14098                 _attr(this.path, {
14099                     "d": makePath(params.d),
14100                     "class": clazz,
14101                     stroke: params.stroke ? params.stroke : null,
14102                     fill: params.fill ? params.fill : null,
14103                     transform: "translate(" + offset[0] + "," + offset[1] + ")"
14104                 });
14105             }
14106         };
14107         var makePath = function (d) {
14108             return (isNaN(d.cxy.x) || isNaN(d.cxy.y)) ? "" : "M" + d.hxy.x + "," + d.hxy.y +
14109                 " L" + d.tail[0].x + "," + d.tail[0].y +
14110                 " L" + d.cxy.x + "," + d.cxy.y +
14111                 " L" + d.tail[1].x + "," + d.tail[1].y +
14112                 " L" + d.hxy.x + "," + d.hxy.y;
14113         };
14114         this.transfer = function(target) {
14115             if (target.canvas && this.path && this.path.parentNode) {
14116                 this.path.parentNode.removeChild(this.path);
14117                 target.canvas.appendChild(this.path);
14118             }
14119         };
14120     };
14121     _ju.extend(AbstractSvgArrowOverlay, [_jp.jsPlumbUIComponent, _jp.Overlays.AbstractOverlay], {
14122         cleanup: function (force) {
14123             if (this.path != null) {
14124                 if (force) {
14125                     this._jsPlumb.instance.removeElement(this.path);
14126                 }
14127                 else {
14128                     if (this.path.parentNode) {
14129                         this.path.parentNode.removeChild(this.path);
14130                     }
14131                 }
14132             }
14133         },
14134         reattach:function(instance) {
14135             if (this.path && this.canvas && this.path.parentNode == null) {
14136                 this.canvas.appendChild(this.path);
14137             }
14138         },
14139         setVisible: function (v) {
14140             if (this.path != null) {
14141                 (this.path.style.display = (v ? "block" : "none"));
14142             }
14143         }
14144     });
14145 
14146     _jp.Overlays.svg.Arrow = function () {
14147         AbstractSvgArrowOverlay.apply(this, [_jp.Overlays.Arrow, arguments]);
14148     };
14149     _ju.extend(_jp.Overlays.svg.Arrow, [ _jp.Overlays.Arrow, AbstractSvgArrowOverlay ]);
14150 
14151     _jp.Overlays.svg.PlainArrow = function () {
14152         AbstractSvgArrowOverlay.apply(this, [_jp.Overlays.PlainArrow, arguments]);
14153     };
14154     _ju.extend(_jp.Overlays.svg.PlainArrow, [ _jp.Overlays.PlainArrow, AbstractSvgArrowOverlay ]);
14155 
14156     _jp.Overlays.svg.Diamond = function () {
14157         AbstractSvgArrowOverlay.apply(this, [_jp.Overlays.Diamond, arguments]);
14158     };
14159     _ju.extend(_jp.Overlays.svg.Diamond, [ _jp.Overlays.Diamond, AbstractSvgArrowOverlay ]);
14160 
14161     // a test
14162     _jp.Overlays.svg.GuideLines = function () {
14163         var path = null, self = this, p1_1, p1_2;
14164         _jp.Overlays.GuideLines.apply(this, arguments);
14165         this.paint = function (params, containerExtents) {
14166             if (path == null) {
14167                 path = _node("path");
14168                 params.connector.svg.appendChild(path);
14169                 self.attachListeners(path, params.connector);
14170                 self.attachListeners(path, self);
14171 
14172                 p1_1 = _node("path");
14173                 params.connector.svg.appendChild(p1_1);
14174                 self.attachListeners(p1_1, params.connector);
14175                 self.attachListeners(p1_1, self);
14176 
14177                 p1_2 = _node("path");
14178                 params.connector.svg.appendChild(p1_2);
14179                 self.attachListeners(p1_2, params.connector);
14180                 self.attachListeners(p1_2, self);
14181             }
14182 
14183             var offset = [0, 0];
14184             if (containerExtents.xmin < 0) {
14185                 offset[0] = -containerExtents.xmin;
14186             }
14187             if (containerExtents.ymin < 0) {
14188                 offset[1] = -containerExtents.ymin;
14189             }
14190 
14191             _attr(path, {
14192                 "d": makePath(params.head, params.tail),
14193                 stroke: "red",
14194                 fill: null,
14195                 transform: "translate(" + offset[0] + "," + offset[1] + ")"
14196             });
14197 
14198             _attr(p1_1, {
14199                 "d": makePath(params.tailLine[0], params.tailLine[1]),
14200                 stroke: "blue",
14201                 fill: null,
14202                 transform: "translate(" + offset[0] + "," + offset[1] + ")"
14203             });
14204 
14205             _attr(p1_2, {
14206                 "d": makePath(params.headLine[0], params.headLine[1]),
14207                 stroke: "green",
14208                 fill: null,
14209                 transform: "translate(" + offset[0] + "," + offset[1] + ")"
14210             });
14211         };
14212 
14213         var makePath = function (d1, d2) {
14214             return "M " + d1.x + "," + d1.y +
14215                 " L" + d2.x + "," + d2.y;
14216         };
14217     };
14218     _ju.extend(_jp.Overlays.svg.GuideLines, _jp.Overlays.GuideLines);
14219 }).call(typeof window !== 'undefined' ? window : this);
14220 
14221 /*
14222  * jsPlumb Community Edition
14223  *
14224  * Provides a way to visually connect elements on an HTML page, using SVG.
14225  * 
14226  * This file contains the 'vanilla' adapter - having no external dependencies other than bundled libs.
14227  *
14228  * Copyright (c) 2010 - 2017 jsPlumb (hello@jsplumbtoolkit.com)
14229  * 
14230  * https://jsplumbtoolkit.com
14231  * https://github.com/jsplumb/jsplumb
14232  * 
14233  * Dual licensed under the MIT and GPL2 licenses.
14234  */
14235 ;
14236 (function () {
14237 
14238     "use strict";
14239     var root = this, _jp = root.jsPlumb, _ju = root.jsPlumbUtil,
14240         _jk = root.Katavorio, _jg = root.Biltong;
14241 
14242     var _getDragManager = function (instance, category) {
14243 
14244         category = category || "main";
14245         var key = "_katavorio_" + category;
14246         var k = instance[key],
14247             e = instance.getEventManager();
14248 
14249         if (!k) {
14250             k = new _jk({
14251                 bind: e.on,
14252                 unbind: e.off,
14253                 getSize: _jp.getSize,
14254                 getPosition: function (el, relativeToRoot) {
14255                     // if this is a nested draggable then compute the offset against its own offsetParent, otherwise
14256                     // compute against the Container's origin. see also the getUIPosition method below.
14257                     var o = instance.getOffset(el, relativeToRoot, el._katavorioDrag ? el.offsetParent : null);
14258                     return [o.left, o.top];
14259                 },
14260                 setPosition: function (el, xy) {
14261                     el.style.left = xy[0] + "px";
14262                     el.style.top = xy[1] + "px";
14263                 },
14264                 addClass: _jp.addClass,
14265                 removeClass: _jp.removeClass,
14266                 intersects: _jg.intersects,
14267                 indexOf: function(l, i) { return l.indexOf(i); },
14268                 scope:instance.getDefaultScope(),
14269                 css: {
14270                     noSelect: instance.dragSelectClass,
14271                     droppable: "jtk-droppable",
14272                     draggable: "jtk-draggable",
14273                     drag: "jtk-drag",
14274                     selected: "jtk-drag-selected",
14275                     active: "jtk-drag-active",
14276                     hover: "jtk-drag-hover",
14277                     ghostProxy:"jtk-ghost-proxy"
14278                 }
14279             });
14280             k.setZoom(instance.getZoom());
14281             instance[key] = k;
14282             instance.bind("zoom", k.setZoom);
14283         }
14284         return k;
14285     };
14286 
14287     var _animProps = function (o, p) {
14288         var _one = function (pName) {
14289             if (p[pName] != null) {
14290                 if (_ju.isString(p[pName])) {
14291                     var m = p[pName].match(/-=/) ? -1 : 1,
14292                         v = p[pName].substring(2);
14293                     return o[pName] + (m * v);
14294                 }
14295                 else {
14296                     return p[pName];
14297                 }
14298             }
14299             else {
14300                 return o[pName];
14301             }
14302         };
14303         return [ _one("left"), _one("top") ];
14304     };
14305 
14306     _jp.extend(root.jsPlumbInstance.prototype, {
14307 
14308         animationSupported:true,
14309         getElement: function (el) {
14310             if (el == null) {
14311                 return null;
14312             }
14313             // here we pluck the first entry if el was a list of entries.
14314             // this is not my favourite thing to do, but previous versions of
14315             // jsplumb supported jquery selectors, and it is possible a selector
14316             // will be passed in here.
14317             el = typeof el === "string" ? el : el.length != null && el.enctype == null ? el[0] : el;
14318             return typeof el === "string" ? document.getElementById(el) : el;
14319         },
14320         removeElement: function (element) {
14321             _getDragManager(this).elementRemoved(element);
14322             this.getEventManager().remove(element);
14323         },
14324         //
14325         // this adapter supports a rudimentary animation function. no easing is supported.  only
14326         // left/top properties are supported. property delta args are expected to be in the form
14327         //
14328         // +=x.xxxx
14329         //
14330         // or
14331         //
14332         // -=x.xxxx
14333         //
14334         doAnimate: function (el, properties, options) {
14335             options = options || {};
14336             var o = this.getOffset(el),
14337                 ap = _animProps(o, properties),
14338                 ldist = ap[0] - o.left,
14339                 tdist = ap[1] - o.top,
14340                 d = options.duration || 250,
14341                 step = 15, steps = d / step,
14342                 linc = (step / d) * ldist,
14343                 tinc = (step / d) * tdist,
14344                 idx = 0,
14345                 _int = setInterval(function () {
14346                     _jp.setPosition(el, {
14347                         left: o.left + (linc * (idx + 1)),
14348                         top: o.top + (tinc * (idx + 1))
14349                     });
14350                     if (options.step != null) {
14351                         options.step(idx, Math.ceil(steps));
14352                     }
14353                     idx++;
14354                     if (idx >= steps) {
14355                         window.clearInterval(_int);
14356                         if (options.complete != null) {
14357                             options.complete();
14358                         }
14359                     }
14360                 }, step);
14361         },
14362         // DRAG/DROP
14363         destroyDraggable: function (el, category) {
14364             _getDragManager(this, category).destroyDraggable(el);
14365         },
14366         destroyDroppable: function (el, category) {
14367             _getDragManager(this, category).destroyDroppable(el);
14368         },
14369         initDraggable: function (el, options, category) {
14370             _getDragManager(this, category).draggable(el, options);
14371         },
14372         initDroppable: function (el, options, category) {
14373             _getDragManager(this, category).droppable(el, options);
14374         },
14375         isAlreadyDraggable: function (el) {
14376             return el._katavorioDrag != null;
14377         },
14378         isDragSupported: function (el, options) {
14379             return true;
14380         },
14381         isDropSupported: function (el, options) {
14382             return true;
14383         },
14384         isElementDraggable: function (el) {
14385             el = _jp.getElement(el);
14386             return el._katavorioDrag && el._katavorioDrag.isEnabled();
14387         },
14388         getDragObject: function (eventArgs) {
14389             return eventArgs[0].drag.getDragElement();
14390         },
14391         getDragScope: function (el) {
14392             return el._katavorioDrag && el._katavorioDrag.scopes.join(" ") || "";
14393         },
14394         getDropEvent: function (args) {
14395             return args[0].e;
14396         },
14397         getUIPosition: function (eventArgs, zoom) {
14398             // here the position reported to us by Katavorio is relative to the element's offsetParent. For top
14399             // level nodes that is fine, but if we have a nested draggable then its offsetParent is actually
14400             // not going to be the jsplumb container; it's going to be some child of that element. In that case
14401             // we want to adjust the UI position to account for the offsetParent's position relative to the Container
14402             // origin.
14403             var el = eventArgs[0].el;
14404             if (el.offsetParent == null) {
14405                 return null;
14406             }
14407             var finalPos = eventArgs[0].finalPos || eventArgs[0].pos;
14408             var p = { left:finalPos[0], top:finalPos[1] };
14409             if (el._katavorioDrag && el.offsetParent !== this.getContainer()) {
14410                 var oc = this.getOffset(el.offsetParent);
14411                 p.left += oc.left;
14412                 p.top += oc.top;
14413             }
14414             return p;
14415         },
14416         setDragFilter: function (el, filter, _exclude) {
14417             if (el._katavorioDrag) {
14418                 el._katavorioDrag.setFilter(filter, _exclude);
14419             }
14420         },
14421         setElementDraggable: function (el, draggable) {
14422             el = _jp.getElement(el);
14423             if (el._katavorioDrag) {
14424                 el._katavorioDrag.setEnabled(draggable);
14425             }
14426         },
14427         setDragScope: function (el, scope) {
14428             if (el._katavorioDrag) {
14429                 el._katavorioDrag.k.setDragScope(el, scope);
14430             }
14431         },
14432         setDropScope:function(el, scope) {
14433             if (el._katavorioDrop && el._katavorioDrop.length > 0) {
14434                 el._katavorioDrop[0].k.setDropScope(el, scope);
14435             }
14436         },
14437         addToPosse:function(el, spec) {
14438             var specs = Array.prototype.slice.call(arguments, 1);
14439             var dm = _getDragManager(this);
14440             _jp.each(el, function(_el) {
14441                 _el = [ _jp.getElement(_el) ];
14442                 _el.push.apply(_el, specs );
14443                 dm.addToPosse.apply(dm, _el);
14444             });
14445         },
14446         setPosse:function(el, spec) {
14447             var specs = Array.prototype.slice.call(arguments, 1);
14448             var dm = _getDragManager(this);
14449             _jp.each(el, function(_el) {
14450                 _el = [ _jp.getElement(_el) ];
14451                 _el.push.apply(_el, specs );
14452                 dm.setPosse.apply(dm, _el);
14453             });
14454         },
14455         removeFromPosse:function(el, posseId) {
14456             var specs = Array.prototype.slice.call(arguments, 1);
14457             var dm = _getDragManager(this);
14458             _jp.each(el, function(_el) {
14459                 _el = [ _jp.getElement(_el) ];
14460                 _el.push.apply(_el, specs );
14461                 dm.removeFromPosse.apply(dm, _el);
14462             });
14463         },
14464         removeFromAllPosses:function(el) {
14465             var dm = _getDragManager(this);
14466             _jp.each(el, function(_el) { dm.removeFromAllPosses(_jp.getElement(_el)); });
14467         },
14468         setPosseState:function(el, posseId, state) {
14469             var dm = _getDragManager(this);
14470             _jp.each(el, function(_el) { dm.setPosseState(_jp.getElement(_el), posseId, state); });
14471         },
14472         dragEvents: {
14473             'start': 'start', 'stop': 'stop', 'drag': 'drag', 'step': 'step',
14474             'over': 'over', 'out': 'out', 'drop': 'drop', 'complete': 'complete',
14475             'beforeStart':'beforeStart'
14476         },
14477         animEvents: {
14478             'step': "step", 'complete': 'complete'
14479         },
14480         stopDrag: function (el) {
14481             if (el._katavorioDrag) {
14482                 el._katavorioDrag.abort();
14483             }
14484         },
14485         addToDragSelection: function (spec) {
14486             _getDragManager(this).select(spec);
14487         },
14488         removeFromDragSelection: function (spec) {
14489             _getDragManager(this).deselect(spec);
14490         },
14491         clearDragSelection: function () {
14492             _getDragManager(this).deselectAll();
14493         },
14494         trigger: function (el, event, originalEvent, payload) {
14495             this.getEventManager().trigger(el, event, originalEvent, payload);
14496         },
14497         doReset:function() {
14498             // look for katavorio instances and reset each one if found.
14499             for (var key in this) {
14500                 if (key.indexOf("_katavorio_") === 0) {
14501                     this[key].reset();
14502                 }
14503             }
14504         }
14505     });
14506 
14507     var ready = function (f) {
14508         var _do = function () {
14509             if (/complete|loaded|interactive/.test(document.readyState) && typeof(document.body) !== "undefined" && document.body != null) {
14510                 f();
14511             }
14512             else {
14513                 setTimeout(_do, 9);
14514             }
14515         };
14516 
14517         _do();
14518     };
14519     ready(_jp.init);
14520 
14521 }).call(typeof window !== 'undefined' ? window : this);
14522